From 86b506e6a6ede38508f6c0cd6539e36c283941cc Mon Sep 17 00:00:00 2001
From: zhandao
Date: Tue, 12 Mar 2024 15:40:48 +0800
Subject: [PATCH 1/7] Underline the keywords in the questions
---
.gitignore | 1 +
lib/nextgen/commands/create.rb | 56 ++++++++++++++++++----------------
2 files changed, 31 insertions(+), 26 deletions(-)
diff --git a/.gitignore b/.gitignore
index 4e0f123..ccdde23 100644
--- a/.gitignore
+++ b/.gitignore
@@ -8,3 +8,4 @@
/spec/reports/
/tmp/
/Gemfile.lock
+/.idea/
diff --git a/lib/nextgen/commands/create.rb b/lib/nextgen/commands/create.rb
index 2fce5d9..665d454 100644
--- a/lib/nextgen/commands/create.rb
+++ b/lib/nextgen/commands/create.rb
@@ -5,6 +5,7 @@
require "open3"
require "tmpdir"
require "tty-prompt"
+require "rainbow"
require "nextgen/ext/prompt/list"
require "nextgen/ext/prompt/multilist"
@@ -26,9 +27,9 @@ def run # rubocop:disable Metrics/MethodLength, Metrics/PerceivedComplexity
say <<~BANNER
Welcome to nextgen, the interactive Rails app generator!
- You are about to create a Rails app named "#{app_name}" in the following directory:
+ You are about to create a Rails app named "#{cyan(app_name)}" in the following directory:
- #{app_path}
+ #{cyan(app_path)}
You'll be asked ~10 questions about database, test framework, and other options.
The standard Rails "omakase" experience will be selected by default.
@@ -83,9 +84,9 @@ def run # rubocop:disable Metrics/MethodLength, Metrics/PerceivedComplexity
say <<~DONE.gsub(/^/, " ")
- #{set_color("Done!", :green)}
+ #{green("Done!")}
- A Rails #{rails_version} app was generated in #{set_color(app_path, :cyan)}.
+ A Rails #{rails_version} app was generated in #{cyan(app_path)}.
Run #{set_color("bin/setup", :yellow)} in that directory to get started.
@@ -99,7 +100,7 @@ def run # rubocop:disable Metrics/MethodLength, Metrics/PerceivedComplexity
def_delegators :shell, :say, :set_color
def continue_if(question)
- if prompt.yes?(question)
+ if prompt.yes?("#{question} ↵")
say
else
say "Canceled", :red
@@ -121,7 +122,7 @@ def rails_version
def ask_rails_version
selected = prompt.select(
- "What version of Rails will you use?",
+ "What #{underline("version")} of Rails will you use?",
Rails.version => :current,
"edge (#{Rails.edge_branch} branch)" => :edge
)
@@ -129,24 +130,21 @@ def ask_rails_version
end
def ask_database
- common_databases = {
+ databases = {
"SQLite3 (default)" => "sqlite3",
"PostgreSQL (recommended)" => "postgresql",
- "MySQL" => "mysql"
- }
- all_databases = common_databases.merge(
- %w[MySQL Trilogy Oracle SQLServer JDBCMySQL JDBCSQLite3 JDBCPostgreSQL JDBC].to_h do |name|
+ **%w[MySQL Trilogy Oracle SQLServer JDBCMySQL JDBCSQLite3 JDBCPostgreSQL JDBC].to_h do |name|
[name, name.downcase]
end,
"None (disable Active Record)" => nil
+ }
+ rails_opts.database = prompt_select(
+ "Which #{underline("database")}?", databases
)
- rails_opts.database =
- prompt.select("Which database?", common_databases.merge("More options..." => false)) ||
- prompt.select("Which database?", all_databases)
end
def ask_full_stack_or_api
- api = prompt.select(
+ api = prompt_select(
"What style of Rails app do you need?",
"Standard, full-stack Rails (default)" => false,
"API only" => true
@@ -155,12 +153,13 @@ def ask_full_stack_or_api
end
def ask_frontend_management
- frontend = prompt.select(
- "How will you manage frontend assets?",
+ frontend = prompt_select(
+ "How will you manage frontend #{underline("assets")}?",
"Sprockets (default)" => "sprockets",
"Propshaft" => "propshaft",
"Vite" => :vite
)
+
if frontend == :vite
rails_opts.asset_pipeline = nil
rails_opts.javascript = "vite"
@@ -170,8 +169,8 @@ def ask_frontend_management
end
def ask_css
- rails_opts.css = prompt.select(
- "Which CSS framework will you use with the asset pipeline?",
+ rails_opts.css = prompt_select(
+ "Which #{underline("CSS")} framework will you use with the asset pipeline?",
"None (default)" => nil,
"Bootstrap" => "bootstrap",
"Bulma" => "bulma",
@@ -182,8 +181,8 @@ def ask_css
end
def ask_javascript
- rails_opts.javascript = prompt.select(
- "Which JavaScript bundler will you use with the asset pipeline?",
+ rails_opts.javascript = prompt_select(
+ "Which #{underline("JavaScript")} bundler will you use with the asset pipeline?",
"Importmap (default)" => "importmap",
"Bun" => "bun",
"ESBuild" => "esbuild",
@@ -214,7 +213,7 @@ def ask_rails_frameworks
end
answers = prompt.multi_select(
- "Which optional Rails frameworks do you need?",
+ "Which optional Rails #{underline("frameworks")} do you need?",
frameworks,
default: frameworks.keys.reverse
)
@@ -223,8 +222,8 @@ def ask_rails_frameworks
end
def ask_test_framework
- rails_opts.test_framework = prompt.select(
- "Which test framework will you use?",
+ rails_opts.test_framework = prompt_select(
+ "Which #{underline("test")} framework will you use?",
"Minitest (default)" => "minitest",
"RSpec" => "rspec",
"None" => nil
@@ -232,8 +231,8 @@ def ask_test_framework
end
def ask_system_testing
- system_testing = prompt.select(
- "Include system testing (capybara)?",
+ system_testing = prompt_select(
+ "Include #{underline("system testing")} (capybara)?",
"Yes (default)" => true,
"No" => false
)
@@ -309,5 +308,10 @@ def prompt
def shell
@shell ||= Thor::Base.shell.new
end
+
+ def green(string) = set_color(string, :green)
+ def cyan(string) = set_color(string, :cyan)
+ def underline(string) = Rainbow(string).underline
+ def prompt_select(question, choices) = prompt.select(question, choices, enum: ".", cycle: true)
end
end
From 55b9d7a299c83e4fba97f6d8c4947ba2935af2d8 Mon Sep 17 00:00:00 2001
From: zhandao
Date: Tue, 12 Mar 2024 17:58:35 +0800
Subject: [PATCH 2/7] Job Backend generator
---
README.md | 8 +-
config/job.yml | 10 +++
lib/nextgen/commands/create.rb | 158 +++++----------------------------
lib/nextgen/generators.rb | 17 ++--
lib/nextgen/helpers.rb | 143 +++++++++++++++++++++++++++++
5 files changed, 194 insertions(+), 142 deletions(-)
create mode 100644 config/job.yml
create mode 100644 lib/nextgen/helpers.rb
diff --git a/README.md b/README.md
index 9aedf61..fb53478 100644
--- a/README.md
+++ b/README.md
@@ -75,6 +75,13 @@ Prefer RSpec? Nextgen can set you up with RSpec, plus the gems and configuration
Nextgen can install and configure your choice of these recommended gems:
+#### Job Backends
+
+- [sidekiq](https://github.com/sidekiq/sidekiq)
+- [solid_queue](https://github.com/basecamp/solid_queue)
+
+#### Other
+
- [annotate](https://github.com/ctran/annotate_models)
- [brakeman](https://github.com/presidentbeef/brakeman)
- [bundler-audit](https://github.com/rubysec/bundler-audit)
@@ -90,7 +97,6 @@ Nextgen can install and configure your choice of these recommended gems:
- [rack-mini-profiler](https://github.com/MiniProfiler/rack-mini-profiler)
- [rubocop](https://github.com/rubocop/rubocop)
- [shoulda-matchers](https://github.com/thoughtbot/shoulda-matchers)
-- [sidekiq](https://github.com/sidekiq/sidekiq)
- [thor](https://github.com/rails/thor)
- [tomo](https://github.com/mattbrictson/tomo)
- [vcr](https://github.com/vcr/vcr)
diff --git a/config/job.yml b/config/job.yml
new file mode 100644
index 0000000..c5083a5
--- /dev/null
+++ b/config/job.yml
@@ -0,0 +1,10 @@
+
+sidekiq:
+ prompt: "Sidekiq (Redis-backed)"
+ description: "Install sidekiq gem to use in production"
+ requires: active_job
+
+solid_queue:
+ prompt: "SolidQueue (Database-backed)"
+ description: "Install solid_queue as ActiveJob's backend"
+ requires: active_job
diff --git a/lib/nextgen/commands/create.rb b/lib/nextgen/commands/create.rb
index 665d454..4f89a04 100644
--- a/lib/nextgen/commands/create.rb
+++ b/lib/nextgen/commands/create.rb
@@ -10,8 +10,9 @@
require "nextgen/ext/prompt/multilist"
module Nextgen
- class Commands::Create # rubocop:disable Metrics/ClassLength
+ class Commands::Create
extend Forwardable
+ include Helpers
def self.run(app_path, options)
new(app_path, options).run
@@ -23,19 +24,8 @@ def initialize(app_path, _options)
@rails_opts = RailsOptions.new
end
- def run # rubocop:disable Metrics/MethodLength, Metrics/PerceivedComplexity
- say <<~BANNER
- Welcome to nextgen, the interactive Rails app generator!
-
- You are about to create a Rails app named "#{cyan(app_name)}" in the following directory:
-
- #{cyan(app_path)}
-
- You'll be asked ~10 questions about database, test framework, and other options.
- The standard Rails "omakase" experience will be selected by default.
-
- BANNER
-
+ def run # rubocop:disable Metrics/PerceivedComplexity
+ say_banner
continue_if "Ready to start?"
ask_rails_version
@@ -47,79 +37,32 @@ def run # rubocop:disable Metrics/MethodLength, Metrics/PerceivedComplexity
ask_rails_frameworks
ask_test_framework
ask_system_testing if rails_opts.frontend? && rails_opts.test_framework?
- ask_optional_enhancements
-
- say <<~SUMMARY
-
- OK! Your Rails app is ready to be created.
- The following options will be passed to `rails new`:
-
- #{rails_new_args.join("\n ")}
- The following nextgen enhancements will also be applied in individual git commits via `rails app:template`:
-
- #{selected_generators.join(", ").scan(/\S.{0,75}(?:,|$)/).join("\n ")}
-
- SUMMARY
-
- if node?
- say <<~NODE
- Based on the options you selected, your app will require Node and Yarn. For reference, you are using these versions:
-
- Node: #{capture_version("node")}
- Yarn: #{capture_version("yarn")}
-
- NODE
+ if prompt.yes?("More detailed configuration? [ cache, job and gems ] ↵")
+ ask_job_backend if rails_opts.active_job?
+ ask_optional_enhancements
end
+ say_summary
+ say_node if node?
continue_if "Continue?"
create_initial_commit_message
copy_package_json if node?
Nextgen::Rails.run "new", *rails_new_args
Dir.chdir(app_path) do
- Nextgen::Rails.run "app:template", "LOCATION=#{write_generators_script}"
+ Nextgen::Rails.run "app:template", "LOCATION=#{write_generators_script(generators)}"
+ Nextgen::Rails.run "app:template", "LOCATION=#{write_generators_script(job_backend)}"
end
-
- say <<~DONE.gsub(/^/, " ")
-
-
- #{green("Done!")}
-
- A Rails #{rails_version} app was generated in #{cyan(app_path)}.
- Run #{set_color("bin/setup", :yellow)} in that directory to get started.
-
-
- DONE
+ say_done
end
private
- attr_accessor :app_path, :app_name, :rails_opts, :generators
+ attr_accessor :app_path, :app_name, :rails_opts, :generators, :job_backend
def_delegators :shell, :say, :set_color
- def continue_if(question)
- if prompt.yes?("#{question} ↵")
- say
- else
- say "Canceled", :red
- exit
- end
- end
-
- def copy_package_json
- FileUtils.mkdir_p(app_path)
- FileUtils.cp(
- Nextgen.template_path.join("package.json"),
- File.join(app_path, "package.json")
- )
- end
-
- def rails_version
- rails_opts.edge? ? "edge (#{Rails.edge_branch} branch)" : Rails.version
- end
-
def ask_rails_version
selected = prompt.select(
"What #{underline("version")} of Rails will you use?",
@@ -239,6 +182,16 @@ def ask_system_testing
rails_opts.skip_system_test! unless system_testing
end
+ def ask_job_backend
+ @job_backend = Generators.compatible_with(rails_opts: rails_opts, scope: "job")
+
+ answer = prompt_select(
+ "Which #{underline("job backend")} would you like to use?",
+ job_backend.optional
+ )
+ job_backend.activate(answer)
+ end
+
def ask_optional_enhancements
@generators = Generators.compatible_with(rails_opts: rails_opts)
@@ -248,70 +201,5 @@ def ask_optional_enhancements
)
generators.activate(*answers)
end
-
- def create_initial_commit_message
- path = File.join(app_path, "tmp", "initial_nextgen_commit")
- FileUtils.mkdir_p(File.dirname(path))
- File.write(path, <<~COMMIT)
- Init project with `rails new` (#{Nextgen::Rails.version})
-
- Nextgen generated this project with the following `rails new` options:
-
- ```
- #{rails_opts.to_args.join("\n")}
- ```
- COMMIT
- end
-
- def rails_new_args
- [app_path, "--no-rc", *rails_opts.to_args].tap do |args|
- # Work around a Rails bug where --edge causes --no-rc to get ignored.
- # Specifying --rc= with a non-existent file has the same effect as --no-rc.
- @rc_token ||= SecureRandom.hex(8)
- args << "--rc=#{@rc_token}" if rails_opts.edge?
- end
- end
-
- def node?
- generators.node_active?
- end
-
- def capture_version(command)
- out, _err, status = Open3.capture3(command, "--version")
- version = status.success? && out[/\d[.\d]+\d/]
-
- version || ""
- end
-
- def selected_generators
- optional = generators.optional.invert
- selected = generators.all_active.filter_map { |name| optional[name] }
-
- selected.any? ? selected.sort_by(&:downcase) : [""]
- end
-
- def write_generators_script
- new_tempfile_path.tap do |location|
- File.write(location, generators.to_ruby_script)
- end
- end
-
- def new_tempfile_path
- token = SecureRandom.hex(8)
- File.join(Dir.tmpdir, "nextgen_create_#{token}.rb")
- end
-
- def prompt
- @prompt ||= TTY::Prompt.new
- end
-
- def shell
- @shell ||= Thor::Base.shell.new
- end
-
- def green(string) = set_color(string, :green)
- def cyan(string) = set_color(string, :cyan)
- def underline(string) = Rainbow(string).underline
- def prompt_select(question, choices) = prompt.select(question, choices, enum: ".", cycle: true)
end
end
diff --git a/lib/nextgen/generators.rb b/lib/nextgen/generators.rb
index 946f193..5bd6dae 100644
--- a/lib/nextgen/generators.rb
+++ b/lib/nextgen/generators.rb
@@ -2,15 +2,15 @@
module Nextgen
class Generators
- def self.compatible_with(rails_opts:)
- yaml_path = File.expand_path("../../config/generators.yml", __dir__)
- new.tap do |g|
+ def self.compatible_with(rails_opts:, scope: "generators")
+ yaml_path = File.expand_path("../../config/#{scope}.yml", __dir__)
+ new.tap do |itself|
YAML.load_file(yaml_path).each do |name, options|
options ||= {}
requirements = Array(options["requires"])
next unless requirements.all? { |req| rails_opts.public_send(:"#{req}?") }
- g.add(
+ itself.add(
name.to_sym,
prompt: options["prompt"],
description: options["description"],
@@ -18,7 +18,7 @@ def self.compatible_with(rails_opts:)
)
end
- g.deactivate_node unless rails_opts.requires_node?
+ itself.deactivate_node unless rails_opts.requires_node?
end
end
@@ -36,6 +36,11 @@ def all_active
end
end
+ def all_active_names
+ opts = optional.invert
+ all_active.filter_map { |name| opts[name] }
+ end
+
def add(name, node: false, prompt: nil, description: nil)
name = name.to_sym
raise ArgumentError, "Generator #{name.inspect} was already added" if generators.key?(name)
@@ -60,7 +65,7 @@ def activate(*optional_generators)
end
def deactivate_node
- generators.fetch(:node)[:active] = false
+ generators[:node][:active] = false if generators.key?(:node)
end
def to_ruby_script
diff --git a/lib/nextgen/helpers.rb b/lib/nextgen/helpers.rb
new file mode 100644
index 0000000..d9d664f
--- /dev/null
+++ b/lib/nextgen/helpers.rb
@@ -0,0 +1,143 @@
+module Nextgen
+ module Helpers
+ private
+
+ def say_banner
+ say <<~BANNER
+ Welcome to nextgen, the interactive Rails app generator!
+
+ You are about to create a Rails app named "#{cyan(app_name)}" in the following directory:
+
+ #{cyan(app_path)}
+
+ You'll be asked ~10 questions about database, test framework, and other options.
+ The standard Rails "omakase" experience will be selected by default.
+
+ BANNER
+ end
+
+ def say_summary
+ say <<~SUMMARY
+
+ OK! Your Rails app is ready to be created.
+ The following options will be passed to `rails new`:
+
+ #{rails_new_args.join("\n ")}
+
+ The following nextgen enhancements will also be applied in individual git commits via `rails app:template`:
+
+ #{activated_generators.join(", ").scan(/\S.{0,75}(?:,|$)/).join("\n ")}
+
+ SUMMARY
+ end
+
+ def say_node
+ say <<~NODE
+ Based on the options you selected, your app will require Node and Yarn. For reference, you are using these versions:
+
+ Node: #{capture_version("node")}
+ Yarn: #{capture_version("yarn")}
+
+ NODE
+ end
+
+ def say_done
+ say <<~DONE.gsub(/^/, " ")
+
+
+ #{green("Done!")}
+
+ A Rails #{rails_version} app was generated in #{cyan(app_path)}.
+ Run #{set_color("bin/setup", :yellow)} in that directory to get started.
+
+
+ DONE
+ end
+
+ def continue_if(question)
+ if prompt.yes?("#{question} ↵")
+ say
+ else
+ say "Canceled", :red
+ exit
+ end
+ end
+
+ def copy_package_json
+ FileUtils.mkdir_p(app_path)
+ FileUtils.cp(
+ Nextgen.template_path.join("package.json"),
+ File.join(app_path, "package.json")
+ )
+ end
+
+ def rails_version
+ rails_opts.edge? ? "edge (#{Rails.edge_branch} branch)" : Rails.version
+ end
+
+ def create_initial_commit_message
+ path = File.join(app_path, "tmp", "initial_nextgen_commit")
+ FileUtils.mkdir_p(File.dirname(path))
+ File.write(path, <<~COMMIT)
+ Init project with `rails new` (#{Nextgen::Rails.version})
+
+ Nextgen generated this project with the following `rails new` options:
+
+ ```
+ #{rails_opts.to_args.join("\n")}
+ ```
+ COMMIT
+ end
+
+ def rails_new_args
+ [app_path, "--no-rc", *rails_opts.to_args].tap do |args|
+ # Work around a Rails bug where --edge causes --no-rc to get ignored.
+ # Specifying --rc= with a non-existent file has the same effect as --no-rc.
+ @rc_token ||= SecureRandom.hex(8)
+ args << "--rc=#{@rc_token}" if rails_opts.edge?
+ end
+ end
+
+ def node?
+ generators.node_active?
+ end
+
+ def capture_version(command)
+ out, _err, status = Open3.capture3(command, "--version")
+ version = status.success? && out[/\d[.\d]+\d/]
+
+ version || ""
+ end
+
+ def activated_generators
+ activated = generators.all_active_names
+ activated.prepend(job_backend.all_active_names.first) unless job_backend.nil?
+
+ activated.any? ? activated.sort_by(&:downcase) : [""]
+ end
+
+ def write_generators_script(g)
+ new_tempfile_path.tap do |location|
+ File.write(location, g.to_ruby_script)
+ end
+ end
+
+ def new_tempfile_path
+ token = SecureRandom.hex(8)
+ File.join(Dir.tmpdir, "nextgen_create_#{token}.rb")
+ end
+
+ def prompt
+ @prompt ||= TTY::Prompt.new
+ end
+
+ def shell
+ @shell ||= Thor::Base.shell.new
+ end
+
+ def green(string) = set_color(string, :green)
+ def cyan(string) = set_color(string, :cyan)
+ def underline(string) = Rainbow(string).underline
+ def prompt_select(question, choices) = prompt.select(question, choices, enum: ".", cycle: true)
+ end
+end
From 4a537e2cba0e9aafafa966cf1afc1755cdf1ff57 Mon Sep 17 00:00:00 2001
From: zhandao
Date: Tue, 12 Mar 2024 21:37:48 +0800
Subject: [PATCH 3/7] Impl solid_queue generator
---
lib/nextgen/commands/create.rb | 7 +++++-
lib/nextgen/{ => commands}/helpers.rb | 2 +-
lib/nextgen/generators.rb | 5 +++++
lib/nextgen/generators/solid_queue.rb | 31 +++++++++++++++++++++++++++
template/config/solid_queue.yml | 18 ++++++++++++++++
5 files changed, 61 insertions(+), 2 deletions(-)
rename lib/nextgen/{ => commands}/helpers.rb (99%)
create mode 100644 lib/nextgen/generators/solid_queue.rb
create mode 100644 template/config/solid_queue.yml
diff --git a/lib/nextgen/commands/create.rb b/lib/nextgen/commands/create.rb
index 4f89a04..2c5180c 100644
--- a/lib/nextgen/commands/create.rb
+++ b/lib/nextgen/commands/create.rb
@@ -12,7 +12,7 @@
module Nextgen
class Commands::Create
extend Forwardable
- include Helpers
+ include Commands::Helpers
def self.run(app_path, options)
new(app_path, options).run
@@ -189,6 +189,11 @@ def ask_job_backend
"Which #{underline("job backend")} would you like to use?",
job_backend.optional
)
+
+ if answer == :solid_queue && prompt.no?(" ↪ Run the SolidQueue supervisor together with Puma (as plugin)?")
+ job_backend.variables[:solid_queue_puma] = true
+ end
+
job_backend.activate(answer)
end
diff --git a/lib/nextgen/helpers.rb b/lib/nextgen/commands/helpers.rb
similarity index 99%
rename from lib/nextgen/helpers.rb
rename to lib/nextgen/commands/helpers.rb
index d9d664f..8c49648 100644
--- a/lib/nextgen/helpers.rb
+++ b/lib/nextgen/commands/helpers.rb
@@ -1,4 +1,4 @@
-module Nextgen
+module Nextgen::Commands
module Helpers
private
diff --git a/lib/nextgen/generators.rb b/lib/nextgen/generators.rb
index 5bd6dae..38705b4 100644
--- a/lib/nextgen/generators.rb
+++ b/lib/nextgen/generators.rb
@@ -18,12 +18,16 @@ def self.compatible_with(rails_opts:, scope: "generators")
)
end
+ itself.variables[:api] = rails_opts.api?
itself.deactivate_node unless rails_opts.requires_node?
end
end
+ attr_accessor :variables
+
def initialize
@generators = {}
+ @variables = {}
end
def node_active?
@@ -78,6 +82,7 @@ def to_ruby_script
<<~SCRIPT
require #{File.expand_path("../nextgen", __dir__).inspect}
extend Nextgen::Actions
+ @variables = #{@variables}
with_nextgen_source_path do
#{apply_statements.join("\n ")}
diff --git a/lib/nextgen/generators/solid_queue.rb b/lib/nextgen/generators/solid_queue.rb
new file mode 100644
index 0000000..22ba403
--- /dev/null
+++ b/lib/nextgen/generators/solid_queue.rb
@@ -0,0 +1,31 @@
+say_git "Install solid_queue as ActiveJob's backend"
+install_gem "solid_queue", version: "~> 0.2"
+
+say_git "Add a solid_queue entry to the Procfile"
+if @variables[:solid_queue_puma]
+ append_to_file "config/puma.rb", "plugin :solid_queue\n"
+else
+ append_to_file "Procfile", "worker: bundle exec rake solid_queue:start\n"
+end
+
+say_git "Configure Active Job to use the solid_queue adapter"
+uncomment_lines "config/environments/production.rb", /config\.active_job/
+gsub_file "config/environments/production.rb",
+ /active_job\.queue_adapter\s*=\s*:.+/,
+ "active_job.queue_adapter = :solid_queue"
+uncomment_lines "config/environments/development.rb", /config\.active_job/
+gsub_file "config/environments/production.rb",
+ /active_job\.queue_adapter\s*=\s*:.+/,
+ "active_job.queue_adapter = :solid_queue"
+copy_file "config/solid_queue.yml"
+
+say_git "Add the SolidQueue migrations"
+system "rails", "solid_queue:install:migrations", exception: true
+
+unless @variables[:api]
+ say_git "Mount the SolidQueue web console at /jobs"
+ install_gem "mission_control-jobs"
+ route "# See [https://github.com/basecamp/mission_control-jobs#authentication-and-base-controller-class]"
+ route '# MissionControl::Jobs.base_controller_class = "AdminController"'
+ route 'mount MissionControl::Jobs::Engine, at: "/jobs"'
+end
diff --git a/template/config/solid_queue.yml b/template/config/solid_queue.yml
new file mode 100644
index 0000000..7a33b9c
--- /dev/null
+++ b/template/config/solid_queue.yml
@@ -0,0 +1,18 @@
+ default: &default
+ dispatchers:
+ - polling_interval: 1
+ batch_size: 500
+ workers:
+ - queues: "*"
+ threads: 5
+ processes: <%= Integer(ENV.fetch("WEB_CONCURRENCY") { Concurrent.physical_processor_count }) %>
+ polling_interval: 0.1
+
+ development:
+ <<: *default
+
+ test:
+ <<: *default
+
+ production:
+ <<: *default
From 66e5285fa7b2f039d68d71f6f534b6fdc65c2976 Mon Sep 17 00:00:00 2001
From: zhandao
Date: Tue, 12 Mar 2024 23:23:12 +0800
Subject: [PATCH 4/7] Second level questions support
---
config/generators.yml | 4 ++
config/job.yml | 4 ++
lib/nextgen.rb | 4 +-
lib/nextgen/commands/create.rb | 36 ++++++-----------
lib/nextgen/commands/helpers.rb | 3 +-
lib/nextgen/generators.rb | 39 ++++++++++++++-----
lib/nextgen/generators/basic/tailwind.rb | 4 ++
lib/nextgen/generators/{ => job}/sidekiq.rb | 0
.../generators/{ => job}/solid_queue.rb | 13 ++++---
lib/nextgen/generators/node.rb | 12 +++---
10 files changed, 70 insertions(+), 49 deletions(-)
create mode 100644 lib/nextgen/generators/basic/tailwind.rb
rename lib/nextgen/generators/{ => job}/sidekiq.rb (100%)
rename lib/nextgen/generators/{ => job}/solid_queue.rb (83%)
diff --git a/config/generators.yml b/config/generators.yml
index 74e5c4f..988f14b 100644
--- a/config/generators.yml
+++ b/config/generators.yml
@@ -137,6 +137,10 @@ vcr:
prompt: "VCR"
description: "Install and configure vcr and webmock gems"
requires: test_framework
+ questions:
+ - variable: solid_queue_puma__no
+ method: no?
+ question: "Run the SolidQueue supervisor together with Puma (as plugin)?"
rubocop:
prompt: "RuboCop"
diff --git a/config/job.yml b/config/job.yml
index c5083a5..c549437 100644
--- a/config/job.yml
+++ b/config/job.yml
@@ -8,3 +8,7 @@ solid_queue:
prompt: "SolidQueue (Database-backed)"
description: "Install solid_queue as ActiveJob's backend"
requires: active_job
+ questions:
+ - variable: solid_queue_puma__no
+ method: no?
+ question: "Run the SolidQueue supervisor together with Puma (as plugin)?"
diff --git a/lib/nextgen.rb b/lib/nextgen.rb
index df2e244..7f8c2a2 100644
--- a/lib/nextgen.rb
+++ b/lib/nextgen.rb
@@ -7,8 +7,8 @@
loader.setup
module Nextgen
- def self.generators_path
- Pathname.new(__dir__).join("nextgen/generators")
+ def self.generators_path(scope = "")
+ Pathname.new(__dir__).join("nextgen/generators", scope)
end
def self.template_path
diff --git a/lib/nextgen/commands/create.rb b/lib/nextgen/commands/create.rb
index 2c5180c..2239fba 100644
--- a/lib/nextgen/commands/create.rb
+++ b/lib/nextgen/commands/create.rb
@@ -37,6 +37,7 @@ def run # rubocop:disable Metrics/PerceivedComplexity
ask_rails_frameworks
ask_test_framework
ask_system_testing if rails_opts.frontend? && rails_opts.test_framework?
+ say
if prompt.yes?("More detailed configuration? [ cache, job and gems ] ↵")
ask_job_backend if rails_opts.active_job?
@@ -81,13 +82,13 @@ def ask_database
end,
"None (disable Active Record)" => nil
}
- rails_opts.database = prompt_select(
+ rails_opts.database = select(
"Which #{underline("database")}?", databases
)
end
def ask_full_stack_or_api
- api = prompt_select(
+ api = select(
"What style of Rails app do you need?",
"Standard, full-stack Rails (default)" => false,
"API only" => true
@@ -96,7 +97,7 @@ def ask_full_stack_or_api
end
def ask_frontend_management
- frontend = prompt_select(
+ frontend = select(
"How will you manage frontend #{underline("assets")}?",
"Sprockets (default)" => "sprockets",
"Propshaft" => "propshaft",
@@ -112,7 +113,7 @@ def ask_frontend_management
end
def ask_css
- rails_opts.css = prompt_select(
+ rails_opts.css = select(
"Which #{underline("CSS")} framework will you use with the asset pipeline?",
"None (default)" => nil,
"Bootstrap" => "bootstrap",
@@ -124,7 +125,7 @@ def ask_css
end
def ask_javascript
- rails_opts.javascript = prompt_select(
+ rails_opts.javascript = select(
"Which #{underline("JavaScript")} bundler will you use with the asset pipeline?",
"Importmap (default)" => "importmap",
"Bun" => "bun",
@@ -155,7 +156,7 @@ def ask_rails_frameworks
)
end
- answers = prompt.multi_select(
+ answers = multi_select(
"Which optional Rails #{underline("frameworks")} do you need?",
frameworks,
default: frameworks.keys.reverse
@@ -165,7 +166,7 @@ def ask_rails_frameworks
end
def ask_test_framework
- rails_opts.test_framework = prompt_select(
+ rails_opts.test_framework = select(
"Which #{underline("test")} framework will you use?",
"Minitest (default)" => "minitest",
"RSpec" => "rspec",
@@ -174,7 +175,7 @@ def ask_test_framework
end
def ask_system_testing
- system_testing = prompt_select(
+ system_testing = select(
"Include #{underline("system testing")} (capybara)?",
"Yes (default)" => true,
"No" => false
@@ -184,27 +185,12 @@ def ask_system_testing
def ask_job_backend
@job_backend = Generators.compatible_with(rails_opts: rails_opts, scope: "job")
-
- answer = prompt_select(
- "Which #{underline("job backend")} would you like to use?",
- job_backend.optional
- )
-
- if answer == :solid_queue && prompt.no?(" ↪ Run the SolidQueue supervisor together with Puma (as plugin)?")
- job_backend.variables[:solid_queue_puma] = true
- end
-
- job_backend.activate(answer)
+ job_backend.ask_select("Which #{underline("job backend")} would you like to use?")
end
def ask_optional_enhancements
@generators = Generators.compatible_with(rails_opts: rails_opts)
-
- answers = prompt.multi_select(
- "Which optional enhancements would you like to add?",
- generators.optional.sort_by { |label, _| label.downcase }.to_h
- )
- generators.activate(*answers)
+ generators.ask_select("Which optional enhancements would you like to add?", multi: true, sort: true)
end
end
end
diff --git a/lib/nextgen/commands/helpers.rb b/lib/nextgen/commands/helpers.rb
index 8c49648..8341b2e 100644
--- a/lib/nextgen/commands/helpers.rb
+++ b/lib/nextgen/commands/helpers.rb
@@ -138,6 +138,7 @@ def shell
def green(string) = set_color(string, :green)
def cyan(string) = set_color(string, :cyan)
def underline(string) = Rainbow(string).underline
- def prompt_select(question, choices) = prompt.select(question, choices, enum: ".", cycle: true)
+ def select(question, choices) = prompt.select(question, choices, enum: ".", cycle: true)
+ def multi_select(question, choices, **opts) = prompt.multi_select(question, choices, filter: true, cycle: true, **opts)
end
end
diff --git a/lib/nextgen/generators.rb b/lib/nextgen/generators.rb
index 38705b4..5bf47fc 100644
--- a/lib/nextgen/generators.rb
+++ b/lib/nextgen/generators.rb
@@ -4,30 +4,49 @@ module Nextgen
class Generators
def self.compatible_with(rails_opts:, scope: "generators")
yaml_path = File.expand_path("../../config/#{scope}.yml", __dir__)
- new.tap do |itself|
+ new(scope).tap do |generators|
YAML.load_file(yaml_path).each do |name, options|
options ||= {}
requirements = Array(options["requires"])
next unless requirements.all? { |req| rails_opts.public_send(:"#{req}?") }
- itself.add(
+ generators.add(
name.to_sym,
prompt: options["prompt"],
description: options["description"],
- node: !!options["node"]
+ node: !!options["node"],
+ questions: options["questions"]
)
end
- itself.variables[:api] = rails_opts.api?
- itself.deactivate_node unless rails_opts.requires_node?
+ generators.variables[:api] = rails_opts.api?
+ generators.deactivate_node unless rails_opts.requires_node?
end
end
attr_accessor :variables
- def initialize
+ def initialize(scope)
@generators = {}
@variables = {}
+ @scope = scope
+ end
+
+ def ask_select(question, multi: false, sort: false)
+ prompt = TTY::Prompt.new
+ opt = sort ? optional.sort_by { |label, _| label.downcase }.to_h : optional
+ args = [question, opt, {cycle: true, filter: true}]
+ answers = multi ? prompt.multi_select(*args) : [prompt.select(*args)]
+
+ answers.each do |answer|
+ second_level_questions = generators[answer][:questions] || []
+ second_level_questions.each do |q|
+ variables[q.fetch("variable")] = prompt.public_send(
+ q.fetch("method"), " ↪ #{q.fetch("question")}"
+ )
+ end
+ end
+ activate(*answers)
end
def node_active?
@@ -45,11 +64,11 @@ def all_active_names
all_active.filter_map { |name| opts[name] }
end
- def add(name, node: false, prompt: nil, description: nil)
+ def add(name, node: false, prompt: nil, description: nil, questions: nil)
name = name.to_sym
raise ArgumentError, "Generator #{name.inspect} was already added" if generators.key?(name)
- generators[name] = {node: node, prompt: prompt, description: description}
+ generators[name] = {node: node, prompt: prompt, description: description, questions: questions}
activate(name) unless prompt
end
@@ -75,7 +94,7 @@ def deactivate_node
def to_ruby_script
apply_statements = all_active.map do |generator|
description = generators.fetch(generator)[:description]
- path = Nextgen.generators_path.join("#{generator}.rb")
+ path = Nextgen.generators_path(scope).join("#{generator}.rb")
"apply_as_git_commit #{path.to_s.inspect}, message: #{description.inspect}"
end
@@ -92,6 +111,6 @@ def to_ruby_script
private
- attr_reader :generators
+ attr_reader :generators, :scope
end
end
diff --git a/lib/nextgen/generators/basic/tailwind.rb b/lib/nextgen/generators/basic/tailwind.rb
new file mode 100644
index 0000000..90f4aee
--- /dev/null
+++ b/lib/nextgen/generators/basic/tailwind.rb
@@ -0,0 +1,4 @@
+if @variables[:tailwind_puma]
+ say_git "Add tailwindcss plugin to puma.rb"
+ append_to_file "config/puma.rb", "plugin :tailwindcss\n"
+end
diff --git a/lib/nextgen/generators/sidekiq.rb b/lib/nextgen/generators/job/sidekiq.rb
similarity index 100%
rename from lib/nextgen/generators/sidekiq.rb
rename to lib/nextgen/generators/job/sidekiq.rb
diff --git a/lib/nextgen/generators/solid_queue.rb b/lib/nextgen/generators/job/solid_queue.rb
similarity index 83%
rename from lib/nextgen/generators/solid_queue.rb
rename to lib/nextgen/generators/job/solid_queue.rb
index 22ba403..7bf537c 100644
--- a/lib/nextgen/generators/solid_queue.rb
+++ b/lib/nextgen/generators/job/solid_queue.rb
@@ -1,11 +1,12 @@
say_git "Install solid_queue as ActiveJob's backend"
install_gem "solid_queue", version: "~> 0.2"
-say_git "Add a solid_queue entry to the Procfile"
-if @variables[:solid_queue_puma]
- append_to_file "config/puma.rb", "plugin :solid_queue\n"
-else
+if @variables[:solid_queue_puma__no]
+ say_git "Add a solid_queue entry to the Procfile"
append_to_file "Procfile", "worker: bundle exec rake solid_queue:start\n"
+else
+ say_git "Add solid_queue plugin to puma.rb"
+ append_to_file "config/puma.rb", "plugin :solid_queue\n"
end
say_git "Configure Active Job to use the solid_queue adapter"
@@ -19,11 +20,11 @@
"active_job.queue_adapter = :solid_queue"
copy_file "config/solid_queue.yml"
-say_git "Add the SolidQueue migrations"
+say_git "Add the solid_queue migrations"
system "rails", "solid_queue:install:migrations", exception: true
unless @variables[:api]
- say_git "Mount the SolidQueue web console at /jobs"
+ say_git "Mount the solid_queue web console at /jobs"
install_gem "mission_control-jobs"
route "# See [https://github.com/basecamp/mission_control-jobs#authentication-and-base-controller-class]"
route '# MissionControl::Jobs.base_controller_class = "AdminController"'
diff --git a/lib/nextgen/generators/node.rb b/lib/nextgen/generators/node.rb
index a771e2e..794ae0e 100644
--- a/lib/nextgen/generators/node.rb
+++ b/lib/nextgen/generators/node.rb
@@ -1,5 +1,7 @@
-say_git "Add Node and Yarn prerequisites"
-copy_file "package.json" unless File.exist?("package.json")
-inject_into_file "README.md", "\n- Node 18 (LTS) or newer\n- Yarn 1.x (classic)", after: /^- Ruby.*$/
-inject_into_file "README.md", "\nbrew install node\nbrew install yarn", after: /^brew install rbenv.*$/
-gitignore "node_modules/"
+unless File.read(".gitignore").match?("node_modules")
+ say_git "Add Node and Yarn prerequisites"
+ copy_file "package.json" unless File.exist?("package.json")
+ inject_into_file "README.md", "\n- Node 18 (LTS) or newer\n- Yarn 1.x (classic)", after: /^- Ruby.*$/
+ inject_into_file "README.md", "\nbrew install node\nbrew install yarn", after: /^brew install rbenv.*$/
+ gitignore "node_modules/"
+end
From 4a8b76dde03151a1970c15e119d7214aed0fd00c Mon Sep 17 00:00:00 2001
From: zhandao
Date: Wed, 13 Mar 2024 00:54:29 +0800
Subject: [PATCH 5/7] Grouping generators
---
config/basic.yml | 29 ++++
config/checkers.yml | 41 +++++
config/code_snippets.yml | 8 +
config/gems.yml | 69 ++++++++
config/generators.yml | 155 ------------------
config/job.yml | 2 +-
config/workflows.yml | 8 +
lib/nextgen/commands/create.rb | 42 ++++-
lib/nextgen/commands/helpers.rb | 8 +-
lib/nextgen/generators.rb | 22 +--
.../generators/{ => basic}/action_mailer.rb | 0
lib/nextgen/generators/{ => basic}/base.rb | 0
.../generators/{ => basic}/clean_gemfile.rb | 0
.../generators/{ => basic}/git_safe.rb | 0
.../{ => basic}/initial_git_commit.rb | 0
.../generators/{ => basic}/rspec_rails.rb | 0
.../{ => basic}/rspec_system_testing.rb | 0
lib/nextgen/generators/basic/tailwind.rb | 5 +-
.../generators/{ => checkers}/brakeman.rb | 0
.../{ => checkers}/bundler_audit.rb | 0
.../generators/{ => checkers}/erb_lint.rb | 0
.../generators/{ => checkers}/eslint.rb | 0
.../{ => checkers}/good_migrations.rb | 0
lib/nextgen/generators/{ => checkers}/node.rb | 0
.../generators/{ => checkers}/overcommit.rb | 0
.../generators/{ => checkers}/rubocop.rb | 0
.../generators/{ => checkers}/stylelint.rb | 0
.../{ => code_snippets}/basic_auth.rb | 0
.../{ => code_snippets}/home_controller.rb | 0
lib/nextgen/generators/{ => gems}/annotate.rb | 0
.../{ => gems}/capybara_lockstep.rb | 0
lib/nextgen/generators/{ => gems}/dotenv.rb | 0
.../{ => gems}/factory_bot_rails.rb | 0
.../generators/{ => gems}/letter_opener.rb | 0
lib/nextgen/generators/gems/node.rb | 7 +
.../{ => gems}/open_browser_on_start.rb | 0
.../generators/{ => gems}/pgcli_rails.rb | 0
.../{ => gems}/rack_canonical_host.rb | 0
.../{ => gems}/rack_mini_profiler.rb | 0
lib/nextgen/generators/{ => gems}/shoulda.rb | 0
lib/nextgen/generators/{ => gems}/thor.rb | 0
lib/nextgen/generators/{ => gems}/tomo.rb | 0
lib/nextgen/generators/{ => gems}/vcr.rb | 0
lib/nextgen/generators/{ => gems}/vite.rb | 0
lib/nextgen/generators/job/solid_queue.rb | 12 +-
.../{ => workflows}/github_actions.rb | 0
.../{ => workflows}/github_pr_template.rb | 0
lib/nextgen/rails_options.rb | 4 +
48 files changed, 225 insertions(+), 187 deletions(-)
create mode 100644 config/basic.yml
create mode 100644 config/checkers.yml
create mode 100644 config/code_snippets.yml
create mode 100644 config/gems.yml
delete mode 100644 config/generators.yml
create mode 100644 config/workflows.yml
rename lib/nextgen/generators/{ => basic}/action_mailer.rb (100%)
rename lib/nextgen/generators/{ => basic}/base.rb (100%)
rename lib/nextgen/generators/{ => basic}/clean_gemfile.rb (100%)
rename lib/nextgen/generators/{ => basic}/git_safe.rb (100%)
rename lib/nextgen/generators/{ => basic}/initial_git_commit.rb (100%)
rename lib/nextgen/generators/{ => basic}/rspec_rails.rb (100%)
rename lib/nextgen/generators/{ => basic}/rspec_system_testing.rb (100%)
rename lib/nextgen/generators/{ => checkers}/brakeman.rb (100%)
rename lib/nextgen/generators/{ => checkers}/bundler_audit.rb (100%)
rename lib/nextgen/generators/{ => checkers}/erb_lint.rb (100%)
rename lib/nextgen/generators/{ => checkers}/eslint.rb (100%)
rename lib/nextgen/generators/{ => checkers}/good_migrations.rb (100%)
rename lib/nextgen/generators/{ => checkers}/node.rb (100%)
rename lib/nextgen/generators/{ => checkers}/overcommit.rb (100%)
rename lib/nextgen/generators/{ => checkers}/rubocop.rb (100%)
rename lib/nextgen/generators/{ => checkers}/stylelint.rb (100%)
rename lib/nextgen/generators/{ => code_snippets}/basic_auth.rb (100%)
rename lib/nextgen/generators/{ => code_snippets}/home_controller.rb (100%)
rename lib/nextgen/generators/{ => gems}/annotate.rb (100%)
rename lib/nextgen/generators/{ => gems}/capybara_lockstep.rb (100%)
rename lib/nextgen/generators/{ => gems}/dotenv.rb (100%)
rename lib/nextgen/generators/{ => gems}/factory_bot_rails.rb (100%)
rename lib/nextgen/generators/{ => gems}/letter_opener.rb (100%)
create mode 100644 lib/nextgen/generators/gems/node.rb
rename lib/nextgen/generators/{ => gems}/open_browser_on_start.rb (100%)
rename lib/nextgen/generators/{ => gems}/pgcli_rails.rb (100%)
rename lib/nextgen/generators/{ => gems}/rack_canonical_host.rb (100%)
rename lib/nextgen/generators/{ => gems}/rack_mini_profiler.rb (100%)
rename lib/nextgen/generators/{ => gems}/shoulda.rb (100%)
rename lib/nextgen/generators/{ => gems}/thor.rb (100%)
rename lib/nextgen/generators/{ => gems}/tomo.rb (100%)
rename lib/nextgen/generators/{ => gems}/vcr.rb (100%)
rename lib/nextgen/generators/{ => gems}/vite.rb (100%)
rename lib/nextgen/generators/{ => workflows}/github_actions.rb (100%)
rename lib/nextgen/generators/{ => workflows}/github_pr_template.rb (100%)
diff --git a/config/basic.yml b/config/basic.yml
new file mode 100644
index 0000000..71c20e3
--- /dev/null
+++ b/config/basic.yml
@@ -0,0 +1,29 @@
+initial_git_commit:
+
+base:
+ description: "Enhance base Rails template with better docs, etc"
+
+clean_gemfile:
+ description: "Clean up Gemfile"
+
+git_safe:
+
+action_mailer:
+ description: "Improve Action Mailer support for absolute URLs and testing"
+ requires: action_mailer
+
+rspec_rails:
+ description: "Install and configure rspec-rails"
+ requires: rspec
+
+rspec_system_testing:
+ description: "Install capybara + selenium-webdriver and set up system specs"
+ requires:
+ - rspec
+ - system_testing
+
+tailwind:
+ questions:
+ - variable: tailwind_puma__no
+ method: no?
+ question: "Integrate tailwind watch with Puma (as plugin)? *defaults to NO*"
diff --git a/config/checkers.yml b/config/checkers.yml
new file mode 100644
index 0000000..ed583e4
--- /dev/null
+++ b/config/checkers.yml
@@ -0,0 +1,41 @@
+
+node:
+ description: "Set up Node and Yarn"
+
+brakeman:
+ prompt: "Brakeman"
+ description: "Install brakeman gem for security checks"
+
+bundler_audit:
+ prompt: "Bundler Audit"
+ description: "Install bundler-audit gem to detect CVEs in Ruby dependencies"
+
+erb_lint:
+ prompt: "ERB Lint"
+ description: "Install erb_lint gem and correct existing issues"
+ requires: frontend
+
+eslint:
+ prompt: "ESLint"
+ description: "Install eslint + supporting packages; apply prettier format"
+ requires: frontend
+ node: true
+
+good_migrations:
+ prompt: "good_migrations"
+ description: "Install good_migrations gem"
+ requires: active_record
+
+stylelint:
+ prompt: "Stylelint"
+ description: "Install stylelint and apply prettier format to CSS"
+ requires: frontend
+ node: true
+
+rubocop:
+ prompt: "RuboCop"
+ description: "Install rubocop gems; apply formatting rules"
+
+overcommit:
+ prompt: "Overcommit"
+ description: "Configure overcommit pre-commit git hooks"
diff --git a/config/code_snippets.yml b/config/code_snippets.yml
new file mode 100644
index 0000000..b384807
--- /dev/null
+++ b/config/code_snippets.yml
@@ -0,0 +1,8 @@
+
+home_controller:
+ description: "Create a controller, view, and route for the home page"
+ requires: frontend
+
+basic_auth:
+ prompt: "BasicAuth controller concern"
+ description: "Allow app to be secured with ENV-based basic auth credentials"
diff --git a/config/gems.yml b/config/gems.yml
new file mode 100644
index 0000000..1106faf
--- /dev/null
+++ b/config/gems.yml
@@ -0,0 +1,69 @@
+
+node:
+ description: "Set up Node and Yarn"
+
+vite:
+ description: "Replace the asset pipeline with Vite in app/frontend"
+ requires: vite
+ node: true
+
+annotate:
+ prompt: "Annotate Models"
+ description: "Install annotate gem to auto-generate schema annotations"
+ requires: active_record
+
+capybara_lockstep:
+ prompt: "capybara-lockstep"
+ description: "Install capybara-lockstep gem for less-flaky browser testing"
+ requires: system_testing
+
+dotenv:
+ prompt: "dotenv"
+ description: "Install dotenv gem and add .env.sample"
+
+factory_bot_rails:
+ prompt: "Factory Bot"
+ description: "Install and configure factory_bot_rails gem"
+ requires: active_record
+
+letter_opener:
+ prompt: "letter_opener"
+ description: "Install letter_opener gem to use with Action Mailer in dev"
+ requires: action_mailer
+
+open_browser_on_start:
+ prompt: "Open browser on startup"
+ description: "Configure puma to launch browser on startup in development"
+ requires: frontend
+
+pgcli_rails:
+ prompt: "pgcli_rails"
+ description: "Install pgcli_rails gem to allow easy use of pgcli"
+ requires: postgresql
+
+rack_canonical_host:
+ prompt: "rack-canonical-host"
+ description: "Install rack-canonical-host gem; use RAILS_HOSTNAME"
+
+rack_mini_profiler:
+ prompt: "rack-mini-profiler"
+ description: "Install rack-mini-profiler gem in development"
+ requires: frontend
+
+shoulda:
+ prompt: "shoulda"
+ description: "Install shoulda-matchers gem for concise model testing"
+ requires: test_framework
+
+thor:
+ prompt: "Thor"
+ description: "Configure Thor for ease of writing Rails tasks"
+
+tomo:
+ prompt: "Tomo"
+ description: "Install tomo gem for SSH-based deployment"
+
+vcr:
+ prompt: "VCR"
+ description: "Install and configure vcr and webmock gems"
+ requires: test_framework
diff --git a/config/generators.yml b/config/generators.yml
deleted file mode 100644
index 988f14b..0000000
--- a/config/generators.yml
+++ /dev/null
@@ -1,155 +0,0 @@
-initial_git_commit:
-
-base:
- description: "Enhance base Rails template with better docs, etc"
-
-clean_gemfile:
- description: "Clean up Gemfile"
-
-rspec_rails:
- description: "Install and configure rspec-rails"
- requires: rspec
-
-rspec_system_testing:
- description: "Install capybara + selenium-webdriver and set up system specs"
- requires:
- - rspec
- - system_testing
-
-node:
- description: "Set up Node and Yarn"
-
-vite:
- description: "Replace the asset pipeline with Vite in app/frontend"
- requires: vite
- node: true
-
-action_mailer:
- description: "Improve Action Mailer support for absolute URLs and testing"
- requires: action_mailer
-
-annotate:
- prompt: "Annotate Models"
- description: "Install annotate gem to auto-generate schema annotations"
- requires: active_record
-
-basic_auth:
- prompt: "BasicAuth controller concern"
- description: "Allow app to be secured with ENV-based basic auth credentials"
-
-brakeman:
- prompt: "Brakeman"
- description: "Install brakeman gem for security checks"
-
-bundler_audit:
- prompt: "Bundler Audit"
- description: "Install bundler-audit gem to detect CVEs in Ruby dependencies"
-
-capybara_lockstep:
- prompt: "capybara-lockstep"
- description: "Install capybara-lockstep gem for less-flaky browser testing"
- requires: system_testing
-
-dotenv:
- prompt: "dotenv"
- description: "Install dotenv gem and add .env.sample"
-
-erb_lint:
- prompt: "ERB Lint"
- description: "Install erb_lint gem and correct existing issues"
- requires: frontend
-
-eslint:
- prompt: "ESLint"
- description: "Install eslint + supporting packages; apply prettier format"
- requires: frontend
- node: true
-
-factory_bot_rails:
- prompt: "Factory Bot"
- description: "Install and configure factory_bot_rails gem"
- requires: active_record
-
-github_pr_template:
- prompt: "GitHub PR template"
- description: "Add GitHub pull request template"
-
-git_safe:
-
-good_migrations:
- prompt: "good_migrations"
- description: "Install good_migrations gem"
- requires: active_record
-
-home_controller:
- description: "Create a controller, view, and route for the home page"
- requires: frontend
-
-letter_opener:
- prompt: "letter_opener"
- description: "Install letter_opener gem to use with Action Mailer in dev"
- requires: action_mailer
-
-open_browser_on_start:
- prompt: "Open browser on startup"
- description: "Configure puma to launch browser on startup in development"
- requires: frontend
-
-pgcli_rails:
- prompt: "pgcli_rails"
- description: "Install pgcli_rails gem to allow easy use of pgcli"
- requires: postgresql
-
-rack_canonical_host:
- prompt: "rack-canonical-host"
- description: "Install rack-canonical-host gem; use RAILS_HOSTNAME"
-
-rack_mini_profiler:
- prompt: "rack-mini-profiler"
- description: "Install rack-mini-profiler gem in development"
- requires: frontend
-
-shoulda:
- prompt: "shoulda"
- description: "Install shoulda-matchers gem for concise model testing"
- requires: test_framework
-
-sidekiq:
- prompt: "Sidekiq"
- description: "Install sidekiq gem to use in production"
- requires: active_job
-
-stylelint:
- prompt: "Stylelint"
- description: "Install stylelint and apply prettier format to CSS"
- requires: frontend
- node: true
-
-thor:
- prompt: "Thor"
- description: "Configure Thor for ease of writing Rails tasks"
-
-tomo:
- prompt: "Tomo"
- description: "Install tomo gem for SSH-based deployment"
-
-vcr:
- prompt: "VCR"
- description: "Install and configure vcr and webmock gems"
- requires: test_framework
- questions:
- - variable: solid_queue_puma__no
- method: no?
- question: "Run the SolidQueue supervisor together with Puma (as plugin)?"
-
-rubocop:
- prompt: "RuboCop"
- description: "Install rubocop gems; apply formatting rules"
-
-overcommit:
- prompt: "Overcommit"
- description: "Configure overcommit pre-commit git hooks"
-
-github_actions:
- prompt: "GitHub Actions"
- description: "Configure GitHub Actions workflow for CI"
diff --git a/config/job.yml b/config/job.yml
index c549437..71d2fe9 100644
--- a/config/job.yml
+++ b/config/job.yml
@@ -11,4 +11,4 @@ solid_queue:
questions:
- variable: solid_queue_puma__no
method: no?
- question: "Run the SolidQueue supervisor together with Puma (as plugin)?"
+ question: "Run the SolidQueue supervisor together with Puma (as plugin)? *defaults to NO*"
diff --git a/config/workflows.yml b/config/workflows.yml
new file mode 100644
index 0000000..56fadfb
--- /dev/null
+++ b/config/workflows.yml
@@ -0,0 +1,8 @@
+
+github_actions:
+ prompt: "GitHub Actions"
+ description: "Configure GitHub Actions workflow for CI"
+
+github_pr_template:
+ prompt: "GitHub PR template"
+ description: "Add GitHub pull request template"
diff --git a/lib/nextgen/commands/create.rb b/lib/nextgen/commands/create.rb
index 2239fba..599ddd1 100644
--- a/lib/nextgen/commands/create.rb
+++ b/lib/nextgen/commands/create.rb
@@ -22,9 +22,10 @@ def initialize(app_path, _options)
@app_path = File.expand_path(app_path)
@app_name = File.basename(@app_path).gsub(/\W/, "_").squeeze("_").camelize
@rails_opts = RailsOptions.new
+ @generators = {basic: Generators.compatible_with(rails_opts: rails_opts, scope: "basic")}
end
- def run # rubocop:disable Metrics/PerceivedComplexity
+ def run # rubocop:disable Metrics/MethodLength Metrics/PerceivedComplexity
say_banner
continue_if "Ready to start?"
@@ -41,6 +42,9 @@ def run # rubocop:disable Metrics/PerceivedComplexity
if prompt.yes?("More detailed configuration? [ cache, job and gems ] ↵")
ask_job_backend if rails_opts.active_job?
+ ask_workflows
+ ask_checkers
+ ask_code_snippets
ask_optional_enhancements
end
@@ -52,15 +56,16 @@ def run # rubocop:disable Metrics/PerceivedComplexity
copy_package_json if node?
Nextgen::Rails.run "new", *rails_new_args
Dir.chdir(app_path) do
- Nextgen::Rails.run "app:template", "LOCATION=#{write_generators_script(generators)}"
- Nextgen::Rails.run "app:template", "LOCATION=#{write_generators_script(job_backend)}"
+ generators.each_value do |g|
+ Nextgen::Rails.run "app:template", "LOCATION=#{write_generators_script(g)}"
+ end
end
say_done
end
private
- attr_accessor :app_path, :app_name, :rails_opts, :generators, :job_backend
+ attr_accessor :app_path, :app_name, :rails_opts, :generators
def_delegators :shell, :say, :set_color
@@ -122,6 +127,7 @@ def ask_css
"Sass" => "sass",
"Tailwind" => "tailwind"
)
+ generators[:basic].ask_second_level_questions(for_selected: rails_opts.css, prompt: prompt)
end
def ask_javascript
@@ -184,13 +190,33 @@ def ask_system_testing
end
def ask_job_backend
- @job_backend = Generators.compatible_with(rails_opts: rails_opts, scope: "job")
- job_backend.ask_select("Which #{underline("job backend")} would you like to use?")
+ generators[:job] = Generators.compatible_with(rails_opts: rails_opts, scope: "job").tap do |it|
+ it.ask_select("Which #{underline("job backend")} would you like to use?", prompt: prompt)
+ end
+ end
+
+ def ask_workflows
+ generators[:workflows] = Generators.compatible_with(rails_opts: rails_opts, scope: "workflows").tap do |it|
+ it.ask_select("Which #{underline("workflows")} would you like to add?", multi: true, prompt: prompt)
+ end
+ end
+
+ def ask_checkers
+ generators[:checkers] = Generators.compatible_with(rails_opts: rails_opts, scope: "checkers").tap do |it|
+ it.ask_select("Which #{underline("checkers")} would you like to add?", multi: true, prompt: prompt)
+ end
+ end
+
+ def ask_code_snippets
+ generators[:code_snippets] = Generators.compatible_with(rails_opts: rails_opts, scope: "code_snippets").tap do |it|
+ it.ask_select("Which #{underline("code snippets")} would you like to add?", multi: true, prompt: prompt)
+ end
end
def ask_optional_enhancements
- @generators = Generators.compatible_with(rails_opts: rails_opts)
- generators.ask_select("Which optional enhancements would you like to add?", multi: true, sort: true)
+ generators[:gems] = Generators.compatible_with(rails_opts: rails_opts, scope: "gems").tap do |it|
+ it.ask_select("Which optional enhancements would you like to add?", multi: true, sort: true, prompt: prompt)
+ end
end
end
end
diff --git a/lib/nextgen/commands/helpers.rb b/lib/nextgen/commands/helpers.rb
index 8341b2e..4005e27 100644
--- a/lib/nextgen/commands/helpers.rb
+++ b/lib/nextgen/commands/helpers.rb
@@ -10,7 +10,7 @@ def say_banner
#{cyan(app_path)}
- You'll be asked ~10 questions about database, test framework, and other options.
+ You'll be asked ~10 (or more) questions about database, test framework, and other options.
The standard Rails "omakase" experience will be selected by default.
BANNER
@@ -99,7 +99,7 @@ def rails_new_args
end
def node?
- generators.node_active?
+ generators.values.any?(&:node_active?)
end
def capture_version(command)
@@ -110,8 +110,8 @@ def capture_version(command)
end
def activated_generators
- activated = generators.all_active_names
- activated.prepend(job_backend.all_active_names.first) unless job_backend.nil?
+ activated = generators[:gems].all_active_names
+ activated.prepend(generators[:job].all_active_names.first) unless generators[:job].nil?
activated.any? ? activated.sort_by(&:downcase) : [""]
end
diff --git a/lib/nextgen/generators.rb b/lib/nextgen/generators.rb
index 5bf47fc..ac1137d 100644
--- a/lib/nextgen/generators.rb
+++ b/lib/nextgen/generators.rb
@@ -2,7 +2,7 @@
module Nextgen
class Generators
- def self.compatible_with(rails_opts:, scope: "generators")
+ def self.compatible_with(rails_opts:, scope:)
yaml_path = File.expand_path("../../config/#{scope}.yml", __dir__)
new(scope).tap do |generators|
YAML.load_file(yaml_path).each do |name, options|
@@ -32,25 +32,27 @@ def initialize(scope)
@scope = scope
end
- def ask_select(question, multi: false, sort: false)
- prompt = TTY::Prompt.new
+ def ask_select(question, multi: false, sort: false, prompt: TTY::Prompt.new)
opt = sort ? optional.sort_by { |label, _| label.downcase }.to_h : optional
args = [question, opt, {cycle: true, filter: true}]
answers = multi ? prompt.multi_select(*args) : [prompt.select(*args)]
answers.each do |answer|
- second_level_questions = generators[answer][:questions] || []
- second_level_questions.each do |q|
- variables[q.fetch("variable")] = prompt.public_send(
- q.fetch("method"), " ↪ #{q.fetch("question")}"
- )
- end
+ ask_second_level_questions(for_selected: answer, prompt: prompt)
end
activate(*answers)
end
+ def ask_second_level_questions(for_selected:, prompt:)
+ (generators[for_selected&.to_sym]&.[](:questions) || []).each do |q|
+ variables[q.fetch("variable").to_sym] = prompt.public_send(
+ q.fetch("method"), " ↪ #{q.fetch("question")}"
+ )
+ end
+ end
+
def node_active?
- !!generators.fetch(:node)[:active]
+ !!generators[:node]&.[](:active)
end
def all_active
diff --git a/lib/nextgen/generators/action_mailer.rb b/lib/nextgen/generators/basic/action_mailer.rb
similarity index 100%
rename from lib/nextgen/generators/action_mailer.rb
rename to lib/nextgen/generators/basic/action_mailer.rb
diff --git a/lib/nextgen/generators/base.rb b/lib/nextgen/generators/basic/base.rb
similarity index 100%
rename from lib/nextgen/generators/base.rb
rename to lib/nextgen/generators/basic/base.rb
diff --git a/lib/nextgen/generators/clean_gemfile.rb b/lib/nextgen/generators/basic/clean_gemfile.rb
similarity index 100%
rename from lib/nextgen/generators/clean_gemfile.rb
rename to lib/nextgen/generators/basic/clean_gemfile.rb
diff --git a/lib/nextgen/generators/git_safe.rb b/lib/nextgen/generators/basic/git_safe.rb
similarity index 100%
rename from lib/nextgen/generators/git_safe.rb
rename to lib/nextgen/generators/basic/git_safe.rb
diff --git a/lib/nextgen/generators/initial_git_commit.rb b/lib/nextgen/generators/basic/initial_git_commit.rb
similarity index 100%
rename from lib/nextgen/generators/initial_git_commit.rb
rename to lib/nextgen/generators/basic/initial_git_commit.rb
diff --git a/lib/nextgen/generators/rspec_rails.rb b/lib/nextgen/generators/basic/rspec_rails.rb
similarity index 100%
rename from lib/nextgen/generators/rspec_rails.rb
rename to lib/nextgen/generators/basic/rspec_rails.rb
diff --git a/lib/nextgen/generators/rspec_system_testing.rb b/lib/nextgen/generators/basic/rspec_system_testing.rb
similarity index 100%
rename from lib/nextgen/generators/rspec_system_testing.rb
rename to lib/nextgen/generators/basic/rspec_system_testing.rb
diff --git a/lib/nextgen/generators/basic/tailwind.rb b/lib/nextgen/generators/basic/tailwind.rb
index 90f4aee..3a86c8a 100644
--- a/lib/nextgen/generators/basic/tailwind.rb
+++ b/lib/nextgen/generators/basic/tailwind.rb
@@ -1,4 +1,5 @@
-if @variables[:tailwind_puma]
+
+unless @variables[:tailwind_puma__no]
say_git "Add tailwindcss plugin to puma.rb"
- append_to_file "config/puma.rb", "plugin :tailwindcss\n"
+ append_to_file "config/puma.rb", %(plugin :tailwindcss if ENV.fetch("RAILS_ENV", "development") == "development"\n)
end
diff --git a/lib/nextgen/generators/brakeman.rb b/lib/nextgen/generators/checkers/brakeman.rb
similarity index 100%
rename from lib/nextgen/generators/brakeman.rb
rename to lib/nextgen/generators/checkers/brakeman.rb
diff --git a/lib/nextgen/generators/bundler_audit.rb b/lib/nextgen/generators/checkers/bundler_audit.rb
similarity index 100%
rename from lib/nextgen/generators/bundler_audit.rb
rename to lib/nextgen/generators/checkers/bundler_audit.rb
diff --git a/lib/nextgen/generators/erb_lint.rb b/lib/nextgen/generators/checkers/erb_lint.rb
similarity index 100%
rename from lib/nextgen/generators/erb_lint.rb
rename to lib/nextgen/generators/checkers/erb_lint.rb
diff --git a/lib/nextgen/generators/eslint.rb b/lib/nextgen/generators/checkers/eslint.rb
similarity index 100%
rename from lib/nextgen/generators/eslint.rb
rename to lib/nextgen/generators/checkers/eslint.rb
diff --git a/lib/nextgen/generators/good_migrations.rb b/lib/nextgen/generators/checkers/good_migrations.rb
similarity index 100%
rename from lib/nextgen/generators/good_migrations.rb
rename to lib/nextgen/generators/checkers/good_migrations.rb
diff --git a/lib/nextgen/generators/node.rb b/lib/nextgen/generators/checkers/node.rb
similarity index 100%
rename from lib/nextgen/generators/node.rb
rename to lib/nextgen/generators/checkers/node.rb
diff --git a/lib/nextgen/generators/overcommit.rb b/lib/nextgen/generators/checkers/overcommit.rb
similarity index 100%
rename from lib/nextgen/generators/overcommit.rb
rename to lib/nextgen/generators/checkers/overcommit.rb
diff --git a/lib/nextgen/generators/rubocop.rb b/lib/nextgen/generators/checkers/rubocop.rb
similarity index 100%
rename from lib/nextgen/generators/rubocop.rb
rename to lib/nextgen/generators/checkers/rubocop.rb
diff --git a/lib/nextgen/generators/stylelint.rb b/lib/nextgen/generators/checkers/stylelint.rb
similarity index 100%
rename from lib/nextgen/generators/stylelint.rb
rename to lib/nextgen/generators/checkers/stylelint.rb
diff --git a/lib/nextgen/generators/basic_auth.rb b/lib/nextgen/generators/code_snippets/basic_auth.rb
similarity index 100%
rename from lib/nextgen/generators/basic_auth.rb
rename to lib/nextgen/generators/code_snippets/basic_auth.rb
diff --git a/lib/nextgen/generators/home_controller.rb b/lib/nextgen/generators/code_snippets/home_controller.rb
similarity index 100%
rename from lib/nextgen/generators/home_controller.rb
rename to lib/nextgen/generators/code_snippets/home_controller.rb
diff --git a/lib/nextgen/generators/annotate.rb b/lib/nextgen/generators/gems/annotate.rb
similarity index 100%
rename from lib/nextgen/generators/annotate.rb
rename to lib/nextgen/generators/gems/annotate.rb
diff --git a/lib/nextgen/generators/capybara_lockstep.rb b/lib/nextgen/generators/gems/capybara_lockstep.rb
similarity index 100%
rename from lib/nextgen/generators/capybara_lockstep.rb
rename to lib/nextgen/generators/gems/capybara_lockstep.rb
diff --git a/lib/nextgen/generators/dotenv.rb b/lib/nextgen/generators/gems/dotenv.rb
similarity index 100%
rename from lib/nextgen/generators/dotenv.rb
rename to lib/nextgen/generators/gems/dotenv.rb
diff --git a/lib/nextgen/generators/factory_bot_rails.rb b/lib/nextgen/generators/gems/factory_bot_rails.rb
similarity index 100%
rename from lib/nextgen/generators/factory_bot_rails.rb
rename to lib/nextgen/generators/gems/factory_bot_rails.rb
diff --git a/lib/nextgen/generators/letter_opener.rb b/lib/nextgen/generators/gems/letter_opener.rb
similarity index 100%
rename from lib/nextgen/generators/letter_opener.rb
rename to lib/nextgen/generators/gems/letter_opener.rb
diff --git a/lib/nextgen/generators/gems/node.rb b/lib/nextgen/generators/gems/node.rb
new file mode 100644
index 0000000..794ae0e
--- /dev/null
+++ b/lib/nextgen/generators/gems/node.rb
@@ -0,0 +1,7 @@
+unless File.read(".gitignore").match?("node_modules")
+ say_git "Add Node and Yarn prerequisites"
+ copy_file "package.json" unless File.exist?("package.json")
+ inject_into_file "README.md", "\n- Node 18 (LTS) or newer\n- Yarn 1.x (classic)", after: /^- Ruby.*$/
+ inject_into_file "README.md", "\nbrew install node\nbrew install yarn", after: /^brew install rbenv.*$/
+ gitignore "node_modules/"
+end
diff --git a/lib/nextgen/generators/open_browser_on_start.rb b/lib/nextgen/generators/gems/open_browser_on_start.rb
similarity index 100%
rename from lib/nextgen/generators/open_browser_on_start.rb
rename to lib/nextgen/generators/gems/open_browser_on_start.rb
diff --git a/lib/nextgen/generators/pgcli_rails.rb b/lib/nextgen/generators/gems/pgcli_rails.rb
similarity index 100%
rename from lib/nextgen/generators/pgcli_rails.rb
rename to lib/nextgen/generators/gems/pgcli_rails.rb
diff --git a/lib/nextgen/generators/rack_canonical_host.rb b/lib/nextgen/generators/gems/rack_canonical_host.rb
similarity index 100%
rename from lib/nextgen/generators/rack_canonical_host.rb
rename to lib/nextgen/generators/gems/rack_canonical_host.rb
diff --git a/lib/nextgen/generators/rack_mini_profiler.rb b/lib/nextgen/generators/gems/rack_mini_profiler.rb
similarity index 100%
rename from lib/nextgen/generators/rack_mini_profiler.rb
rename to lib/nextgen/generators/gems/rack_mini_profiler.rb
diff --git a/lib/nextgen/generators/shoulda.rb b/lib/nextgen/generators/gems/shoulda.rb
similarity index 100%
rename from lib/nextgen/generators/shoulda.rb
rename to lib/nextgen/generators/gems/shoulda.rb
diff --git a/lib/nextgen/generators/thor.rb b/lib/nextgen/generators/gems/thor.rb
similarity index 100%
rename from lib/nextgen/generators/thor.rb
rename to lib/nextgen/generators/gems/thor.rb
diff --git a/lib/nextgen/generators/tomo.rb b/lib/nextgen/generators/gems/tomo.rb
similarity index 100%
rename from lib/nextgen/generators/tomo.rb
rename to lib/nextgen/generators/gems/tomo.rb
diff --git a/lib/nextgen/generators/vcr.rb b/lib/nextgen/generators/gems/vcr.rb
similarity index 100%
rename from lib/nextgen/generators/vcr.rb
rename to lib/nextgen/generators/gems/vcr.rb
diff --git a/lib/nextgen/generators/vite.rb b/lib/nextgen/generators/gems/vite.rb
similarity index 100%
rename from lib/nextgen/generators/vite.rb
rename to lib/nextgen/generators/gems/vite.rb
diff --git a/lib/nextgen/generators/job/solid_queue.rb b/lib/nextgen/generators/job/solid_queue.rb
index 7bf537c..994f1af 100644
--- a/lib/nextgen/generators/job/solid_queue.rb
+++ b/lib/nextgen/generators/job/solid_queue.rb
@@ -10,14 +10,12 @@
end
say_git "Configure Active Job to use the solid_queue adapter"
-uncomment_lines "config/environments/production.rb", /config\.active_job/
gsub_file "config/environments/production.rb",
- /active_job\.queue_adapter\s*=\s*:.+/,
- "active_job.queue_adapter = :solid_queue"
-uncomment_lines "config/environments/development.rb", /config\.active_job/
-gsub_file "config/environments/production.rb",
- /active_job\.queue_adapter\s*=\s*:.+/,
- "active_job.queue_adapter = :solid_queue"
+ /(# )?config\.active_job\.queue_adapter\s+=.*/,
+ "config.active_job.queue_adapter = :solid_queue"
+inject_into_file "config/environments/development.rb",
+ " config.active_job.queue_adapter = :solid_queue\n",
+ after: "config.active_job.verbose_enqueue_logs = true\n"
copy_file "config/solid_queue.yml"
say_git "Add the solid_queue migrations"
diff --git a/lib/nextgen/generators/github_actions.rb b/lib/nextgen/generators/workflows/github_actions.rb
similarity index 100%
rename from lib/nextgen/generators/github_actions.rb
rename to lib/nextgen/generators/workflows/github_actions.rb
diff --git a/lib/nextgen/generators/github_pr_template.rb b/lib/nextgen/generators/workflows/github_pr_template.rb
similarity index 100%
rename from lib/nextgen/generators/github_pr_template.rb
rename to lib/nextgen/generators/workflows/github_pr_template.rb
diff --git a/lib/nextgen/rails_options.rb b/lib/nextgen/rails_options.rb
index 78fc696..8a2e7de 100644
--- a/lib/nextgen/rails_options.rb
+++ b/lib/nextgen/rails_options.rb
@@ -156,6 +156,10 @@ def active_job?
!skip_optional_framework?("active_job")
end
+ def tailwind?
+ css == "tailwind"
+ end
+
def skip_optional_framework!(framework)
raise ArgumentError, "Unknown framework: #{framework}" unless OPTIONAL_FRAMEWORKS.include?(framework)
From 4275a014924833188fd480f811b00ab9617c9dbb Mon Sep 17 00:00:00 2001
From: zhandao
Date: Wed, 13 Mar 2024 01:40:44 +0800
Subject: [PATCH 6/7] Add code snippets `Current`
---
config/code_snippets.yml | 4 ++++
lib/nextgen/commands/create.rb | 4 ++--
lib/nextgen/generators.rb | 11 ++++-------
lib/nextgen/generators/code_snippets/current.rb | 1 +
template/app/models/current.rb | 3 +++
5 files changed, 14 insertions(+), 9 deletions(-)
create mode 100644 lib/nextgen/generators/code_snippets/current.rb
create mode 100644 template/app/models/current.rb
diff --git a/config/code_snippets.yml b/config/code_snippets.yml
index b384807..b9f5f53 100644
--- a/config/code_snippets.yml
+++ b/config/code_snippets.yml
@@ -6,3 +6,7 @@ home_controller:
basic_auth:
prompt: "BasicAuth controller concern"
description: "Allow app to be secured with ENV-based basic auth credentials"
+
+current:
+ prompt: "Current.user"
+ description: "Intro thread-isolated attributes singleton `Current`, which resets automatically before and after each request"
diff --git a/lib/nextgen/commands/create.rb b/lib/nextgen/commands/create.rb
index 599ddd1..de7b086 100644
--- a/lib/nextgen/commands/create.rb
+++ b/lib/nextgen/commands/create.rb
@@ -22,7 +22,6 @@ def initialize(app_path, _options)
@app_path = File.expand_path(app_path)
@app_name = File.basename(@app_path).gsub(/\W/, "_").squeeze("_").camelize
@rails_opts = RailsOptions.new
- @generators = {basic: Generators.compatible_with(rails_opts: rails_opts, scope: "basic")}
end
def run # rubocop:disable Metrics/MethodLength Metrics/PerceivedComplexity
@@ -40,7 +39,7 @@ def run # rubocop:disable Metrics/MethodLength Metrics/PerceivedComplexity
ask_system_testing if rails_opts.frontend? && rails_opts.test_framework?
say
- if prompt.yes?("More detailed configuration? [ cache, job and gems ] ↵")
+ if prompt.yes?("More detailed configuration? [ job, code snippets, gems ... ] ↵")
ask_job_backend if rails_opts.active_job?
ask_workflows
ask_checkers
@@ -99,6 +98,7 @@ def ask_full_stack_or_api
"API only" => true
)
rails_opts.api! if api
+ @generators = {basic: Generators.compatible_with(rails_opts: rails_opts, scope: "basic")}
end
def ask_frontend_management
diff --git a/lib/nextgen/generators.rb b/lib/nextgen/generators.rb
index ac1137d..6342167 100644
--- a/lib/nextgen/generators.rb
+++ b/lib/nextgen/generators.rb
@@ -4,7 +4,7 @@ module Nextgen
class Generators
def self.compatible_with(rails_opts:, scope:)
yaml_path = File.expand_path("../../config/#{scope}.yml", __dir__)
- new(scope).tap do |generators|
+ new(scope, api: rails_opts.api?).tap do |generators|
YAML.load_file(yaml_path).each do |name, options|
options ||= {}
requirements = Array(options["requires"])
@@ -19,16 +19,13 @@ def self.compatible_with(rails_opts:, scope:)
)
end
- generators.variables[:api] = rails_opts.api?
generators.deactivate_node unless rails_opts.requires_node?
end
end
- attr_accessor :variables
-
- def initialize(scope)
+ def initialize(scope, **vars)
@generators = {}
- @variables = {}
+ @variables = vars
@scope = scope
end
@@ -113,6 +110,6 @@ def to_ruby_script
private
- attr_reader :generators, :scope
+ attr_reader :generators, :variables, :scope
end
end
diff --git a/lib/nextgen/generators/code_snippets/current.rb b/lib/nextgen/generators/code_snippets/current.rb
new file mode 100644
index 0000000..7b5ec82
--- /dev/null
+++ b/lib/nextgen/generators/code_snippets/current.rb
@@ -0,0 +1 @@
+copy_file "app/models/current.rb"
diff --git a/template/app/models/current.rb b/template/app/models/current.rb
new file mode 100644
index 0000000..73a9744
--- /dev/null
+++ b/template/app/models/current.rb
@@ -0,0 +1,3 @@
+class Current < ActiveSupport::CurrentAttributes
+ attribute :user
+end
From ff2b6deceec7adb64ac13b6f33dbd0997795e7b8 Mon Sep 17 00:00:00 2001
From: zhandao
Date: Fri, 15 Mar 2024 15:45:36 +0800
Subject: [PATCH 7/7] Impl `style` option
---
README.md | 21 +++++---
config/job_backend.yml | 5 ++
config/styles/full/checkers.yml | 0
config/styles/full/code_snippets.yml | 0
config/styles/full/gems.yml | 0
.../{job.yml => styles/full/job_backend.yml} | 5 --
config/styles/full/workflows.yml | 0
lib/nextgen.rb | 24 +++++++++
lib/nextgen/cli.rb | 1 +
lib/nextgen/commands/create.rb | 51 ++++++-------------
lib/nextgen/commands/helpers.rb | 4 +-
lib/nextgen/generators.rb | 12 ++---
.../{job => job_backend}/sidekiq.rb | 0
.../{job => job_backend}/solid_queue.rb | 0
14 files changed, 66 insertions(+), 57 deletions(-)
create mode 100644 config/job_backend.yml
create mode 100644 config/styles/full/checkers.yml
create mode 100644 config/styles/full/code_snippets.yml
create mode 100644 config/styles/full/gems.yml
rename config/{job.yml => styles/full/job_backend.yml} (70%)
create mode 100644 config/styles/full/workflows.yml
rename lib/nextgen/generators/{job => job_backend}/sidekiq.rb (100%)
rename lib/nextgen/generators/{job => job_backend}/solid_queue.rb (100%)
diff --git a/README.md b/README.md
index fb53478..03343bf 100644
--- a/README.md
+++ b/README.md
@@ -45,6 +45,13 @@ gem exec nextgen create myapp
This will download the latest version of the `nextgen` gem and use it to create an app in the `myapp` directory. You'll be asked to configure the tech stack through several interactive prompts. If you have a `~/.railsrc` file, it will be ignored.
+Options:
+- `style`: control the **optional enhancements** you can choose in the generator.
+ - defaults to `default`, [enhancements list](config)
+ - presets:
+ - `full` (`--style=full`), [enhancements list](config/styles/full)
+ - your local configs: `--style=path/to/your/style_dir`
+
> [!TIP]
> If you get an "Unknown command exec" error, fix it by upgrading rubygems: `gem update --system`.
@@ -59,7 +66,7 @@ Check out the [examples directory](./examples) to see some Rails apps that were
On top of that foundation, Nextgen offers dozens of useful enhancements to the vanilla Rails experience. You are free to pick and choose which (if any) of these to apply to your new project. Behind the scenes, **each enhancement is applied in a separate git commit,** so that you can later see what was applied and why, and revert the suggestions if necessary.
> [!TIP]
-> For the full list of what Nextgen provides, check out [config/generators.yml](https://github.com/mattbrictson/nextgen/tree/main/config/generators.yml). The source code of each generator can be found in [lib/nextgen/generators](https://github.com/mattbrictson/nextgen/tree/main/lib/nextgen/generators).
+> For the full list of what Nextgen provides, check out [config/*.yml](https://github.com/mattbrictson/nextgen/tree/main/config). The source code of each generator can be found in [lib/nextgen/generators](https://github.com/mattbrictson/nextgen/tree/main/lib/nextgen/generators).
Here are some highlights of what Nextgen brings to the table:
@@ -71,16 +78,14 @@ Nextgen can optionally set up a GitHub Actions CI workflow for your app that aut
Prefer RSpec? Nextgen can set you up with RSpec, plus the gems and configuration you need for system specs (browser testing). Or stick with the Rails Minitest defaults. In either case, Nextgen will set up a good default Rake task and appropriate CI job.
-### Gems
-
-Nextgen can install and configure your choice of these recommended gems:
-
-#### Job Backends
+### Job Backends
- [sidekiq](https://github.com/sidekiq/sidekiq)
-- [solid_queue](https://github.com/basecamp/solid_queue)
+- [solid_queue](https://github.com/basecamp/solid_queue) (`--style=full`)
+
+### Gems
-#### Other
+Nextgen can install and configure your choice of these recommended gems:
- [annotate](https://github.com/ctran/annotate_models)
- [brakeman](https://github.com/presidentbeef/brakeman)
diff --git a/config/job_backend.yml b/config/job_backend.yml
new file mode 100644
index 0000000..09ea387
--- /dev/null
+++ b/config/job_backend.yml
@@ -0,0 +1,5 @@
+
+sidekiq:
+ prompt: "Sidekiq (Redis-backed)"
+ description: "Install sidekiq gem to use in production"
+ requires: active_job
diff --git a/config/styles/full/checkers.yml b/config/styles/full/checkers.yml
new file mode 100644
index 0000000..e69de29
diff --git a/config/styles/full/code_snippets.yml b/config/styles/full/code_snippets.yml
new file mode 100644
index 0000000..e69de29
diff --git a/config/styles/full/gems.yml b/config/styles/full/gems.yml
new file mode 100644
index 0000000..e69de29
diff --git a/config/job.yml b/config/styles/full/job_backend.yml
similarity index 70%
rename from config/job.yml
rename to config/styles/full/job_backend.yml
index 71d2fe9..bfe741f 100644
--- a/config/job.yml
+++ b/config/styles/full/job_backend.yml
@@ -1,9 +1,4 @@
-sidekiq:
- prompt: "Sidekiq (Redis-backed)"
- description: "Install sidekiq gem to use in production"
- requires: active_job
-
solid_queue:
prompt: "SolidQueue (Database-backed)"
description: "Install solid_queue as ActiveJob's backend"
diff --git a/config/styles/full/workflows.yml b/config/styles/full/workflows.yml
new file mode 100644
index 0000000..e69de29
diff --git a/lib/nextgen.rb b/lib/nextgen.rb
index 7f8c2a2..79349e9 100644
--- a/lib/nextgen.rb
+++ b/lib/nextgen.rb
@@ -14,4 +14,28 @@ def self.generators_path(scope = "")
def self.template_path
Pathname.new(__dir__).join("../template")
end
+
+ def self.config_path(style: nil)
+ if style
+ if style.match?("/")
+ Pathname.new(style)
+ else
+ Pathname.new(__dir__).join("../config/styles", style)
+ end
+ else
+ Pathname.new(__dir__).join("../config")
+ end
+ end
+
+ def self.config_for(scope:, style: nil)
+ base = YAML.load_file("#{Nextgen.config_path}/#{scope}.yml")
+ if style
+ base.merge!(YAML.load_file("#{Nextgen.config_path(style: style)}/#{scope}.yml") || {})
+ end
+ base
+ end
+
+ def self.scopes_for(style: nil)
+ Dir[Nextgen.config_path(style: style) + "*.yml"].map { _1.match(/([_a-z]*)\.yml/)[1] }
+ end
end
diff --git a/lib/nextgen/cli.rb b/lib/nextgen/cli.rb
index 4ba5a94..a4d5197 100644
--- a/lib/nextgen/cli.rb
+++ b/lib/nextgen/cli.rb
@@ -6,6 +6,7 @@ class CLI < Thor
map %w[-v --version] => "version"
+ option :style, type: :string, default: nil
desc "create APP_PATH", "Generate a Rails app interactively in APP_PATH"
def create(app_path)
Commands::Create.run(app_path, options)
diff --git a/lib/nextgen/commands/create.rb b/lib/nextgen/commands/create.rb
index de7b086..9bb2b66 100644
--- a/lib/nextgen/commands/create.rb
+++ b/lib/nextgen/commands/create.rb
@@ -18,13 +18,14 @@ def self.run(app_path, options)
new(app_path, options).run
end
- def initialize(app_path, _options)
+ def initialize(app_path, options)
@app_path = File.expand_path(app_path)
@app_name = File.basename(@app_path).gsub(/\W/, "_").squeeze("_").camelize
@rails_opts = RailsOptions.new
+ @style = options[:style]
end
- def run # rubocop:disable Metrics/MethodLength Metrics/PerceivedComplexity
+ def run # rubocop:disable Metrics/MethodLength, Metrics/PerceivedComplexity
say_banner
continue_if "Ready to start?"
@@ -39,12 +40,8 @@ def run # rubocop:disable Metrics/MethodLength Metrics/PerceivedComplexity
ask_system_testing if rails_opts.frontend? && rails_opts.test_framework?
say
- if prompt.yes?("More detailed configuration? [ job, code snippets, gems ... ] ↵")
- ask_job_backend if rails_opts.active_job?
- ask_workflows
- ask_checkers
- ask_code_snippets
- ask_optional_enhancements
+ if prompt.yes?("More enhancements? [ job, code snippets, gems ... ] ↵")
+ ask_styled_enhancements
end
say_summary
@@ -98,7 +95,7 @@ def ask_full_stack_or_api
"API only" => true
)
rails_opts.api! if api
- @generators = {basic: Generators.compatible_with(rails_opts: rails_opts, scope: "basic")}
+ @generators = {basic: Generators.compatible_with(rails_opts: rails_opts, style: nil, scope: "basic")}
end
def ask_frontend_management
@@ -189,33 +186,17 @@ def ask_system_testing
rails_opts.skip_system_test! unless system_testing
end
- def ask_job_backend
- generators[:job] = Generators.compatible_with(rails_opts: rails_opts, scope: "job").tap do |it|
- it.ask_select("Which #{underline("job backend")} would you like to use?", prompt: prompt)
- end
- end
-
- def ask_workflows
- generators[:workflows] = Generators.compatible_with(rails_opts: rails_opts, scope: "workflows").tap do |it|
- it.ask_select("Which #{underline("workflows")} would you like to add?", multi: true, prompt: prompt)
- end
- end
-
- def ask_checkers
- generators[:checkers] = Generators.compatible_with(rails_opts: rails_opts, scope: "checkers").tap do |it|
- it.ask_select("Which #{underline("checkers")} would you like to add?", multi: true, prompt: prompt)
- end
- end
-
- def ask_code_snippets
- generators[:code_snippets] = Generators.compatible_with(rails_opts: rails_opts, scope: "code_snippets").tap do |it|
- it.ask_select("Which #{underline("code snippets")} would you like to add?", multi: true, prompt: prompt)
- end
- end
+ def ask_styled_enhancements
+ say " ↪ style: #{cyan(@style || "default")}"
+ Nextgen.scopes_for(style: @style).each do |scope|
+ gen = Generators.compatible_with(rails_opts: rails_opts, style: @style, scope: scope)
+ next if gen.empty? || scope == "basic"
- def ask_optional_enhancements
- generators[:gems] = Generators.compatible_with(rails_opts: rails_opts, scope: "gems").tap do |it|
- it.ask_select("Which optional enhancements would you like to add?", multi: true, sort: true, prompt: prompt)
+ key_word = underline(scope.tr("_", " "))
+ multi = scope == scope.pluralize
+ sort = gen.optional.size > 10
+ gen.ask_select("Which #{key_word} would you like to add?", prompt: prompt, multi: multi, sort: sort)
+ generators[scope.to_sym] = gen
end
end
end
diff --git a/lib/nextgen/commands/helpers.rb b/lib/nextgen/commands/helpers.rb
index 4005e27..fcf9566 100644
--- a/lib/nextgen/commands/helpers.rb
+++ b/lib/nextgen/commands/helpers.rb
@@ -110,9 +110,7 @@ def capture_version(command)
end
def activated_generators
- activated = generators[:gems].all_active_names
- activated.prepend(generators[:job].all_active_names.first) unless generators[:job].nil?
-
+ activated = generators.values.flat_map(&:all_active_names)
activated.any? ? activated.sort_by(&:downcase) : [""]
end
diff --git a/lib/nextgen/generators.rb b/lib/nextgen/generators.rb
index 6342167..0105871 100644
--- a/lib/nextgen/generators.rb
+++ b/lib/nextgen/generators.rb
@@ -2,10 +2,9 @@
module Nextgen
class Generators
- def self.compatible_with(rails_opts:, scope:)
- yaml_path = File.expand_path("../../config/#{scope}.yml", __dir__)
+ def self.compatible_with(rails_opts:, style:, scope:)
new(scope, api: rails_opts.api?).tap do |generators|
- YAML.load_file(yaml_path).each do |name, options|
+ Nextgen.config_for(style: style, scope: scope).each do |name, options|
options ||= {}
requirements = Array(options["requires"])
next unless requirements.all? { |req| rails_opts.public_send(:"#{req}?") }
@@ -18,7 +17,6 @@ def self.compatible_with(rails_opts:, scope:)
questions: options["questions"]
)
end
-
generators.deactivate_node unless rails_opts.requires_node?
end
end
@@ -29,9 +27,11 @@ def initialize(scope, **vars)
@scope = scope
end
+ def empty? = @generators.empty?
+
def ask_select(question, multi: false, sort: false, prompt: TTY::Prompt.new)
- opt = sort ? optional.sort_by { |label, _| label.downcase }.to_h : optional
- args = [question, opt, {cycle: true, filter: true}]
+ opts = sort ? optional.sort_by { |label, _| label.downcase }.to_h : optional
+ args = [question, opts, {cycle: true, filter: true}]
answers = multi ? prompt.multi_select(*args) : [prompt.select(*args)]
answers.each do |answer|
diff --git a/lib/nextgen/generators/job/sidekiq.rb b/lib/nextgen/generators/job_backend/sidekiq.rb
similarity index 100%
rename from lib/nextgen/generators/job/sidekiq.rb
rename to lib/nextgen/generators/job_backend/sidekiq.rb
diff --git a/lib/nextgen/generators/job/solid_queue.rb b/lib/nextgen/generators/job_backend/solid_queue.rb
similarity index 100%
rename from lib/nextgen/generators/job/solid_queue.rb
rename to lib/nextgen/generators/job_backend/solid_queue.rb