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,30 @@
|
|
|
1
|
+
say_git "Install the sidekiq gem in the :production group, with a binstub"
|
|
2
|
+
install_gem "sidekiq", version: "~> 7.0", group: :production
|
|
3
|
+
binstub "sidekiq"
|
|
4
|
+
|
|
5
|
+
say_git "Add a sidekiq entry to the Procfile"
|
|
6
|
+
append_to_file "Procfile", "worker: bundle exec sidekiq -C config/sidekiq.yml\n"
|
|
7
|
+
|
|
8
|
+
say_git "Configure Active Job to use the sidekiq adapter in production"
|
|
9
|
+
uncomment_lines "config/environments/production.rb", /config\.active_job/
|
|
10
|
+
gsub_file "config/environments/production.rb",
|
|
11
|
+
/active_job\.queue_adapter\s*=\s*:.+/,
|
|
12
|
+
"active_job.queue_adapter = :sidekiq"
|
|
13
|
+
gsub_file "config/environments/production.rb", " (and separate queues per environment)", ""
|
|
14
|
+
gsub_file "config/environments/production.rb",
|
|
15
|
+
/queue_name_prefix = .*$/,
|
|
16
|
+
"queue_name_prefix = nil # Not supported by sidekiq"
|
|
17
|
+
|
|
18
|
+
say_git "Mount the Sidekiq web console at /sidekiq, secured with basic auth"
|
|
19
|
+
copy_file "config/initializers/sidekiq.rb"
|
|
20
|
+
route 'mount Sidekiq::Web => "/sidekiq" if defined?(Sidekiq)'
|
|
21
|
+
|
|
22
|
+
say_git "Allow SIDEKIQ_CONCURRENCY env var to set concurrency"
|
|
23
|
+
document_deploy_var "SIDEKIQ_CONCURRENCY", "Number of threads used per Sidekiq process", default: "5"
|
|
24
|
+
copy_file "config/sidekiq.yml"
|
|
25
|
+
|
|
26
|
+
if File.exist?("config/database.yml")
|
|
27
|
+
gsub_file "config/database.yml",
|
|
28
|
+
'ENV.fetch("RAILS_MAX_THREADS") { 5 }',
|
|
29
|
+
'[5, *ENV.values_at("RAILS_MAX_THREADS", "SIDEKIQ_CONCURRENCY")].map(&:to_i).max'
|
|
30
|
+
end
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
say_git "Install stylelint"
|
|
2
|
+
add_yarn_packages(
|
|
3
|
+
"stylelint",
|
|
4
|
+
"stylelint-config-standard",
|
|
5
|
+
"stylelint-declaration-strict-value",
|
|
6
|
+
"stylelint-prettier",
|
|
7
|
+
"prettier",
|
|
8
|
+
"npm-run-all",
|
|
9
|
+
dev: true
|
|
10
|
+
)
|
|
11
|
+
add_package_json_scripts(
|
|
12
|
+
"lint:css": "stylelint 'app/{components,frontend,assets/stylesheets}/**/*.css'",
|
|
13
|
+
"fix:css": "npm run -- lint:css --fix",
|
|
14
|
+
lint: "npm-run-all lint:**",
|
|
15
|
+
fix: "npm-run-all fix:**"
|
|
16
|
+
)
|
|
17
|
+
copy_file ".stylelintrc.cjs"
|
|
18
|
+
|
|
19
|
+
say_git "Add stylelint to default rake task"
|
|
20
|
+
copy_file "lib/tasks/stylelint.rake"
|
|
21
|
+
add_lint_task "stylelint"
|
|
22
|
+
|
|
23
|
+
say_git "Auto-correct any existing issues"
|
|
24
|
+
run "yarn fix:css", capture: true
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
say_git "Install the tomo gem in the :development group, with a binstub"
|
|
2
|
+
install_gem "tomo", version: "~> 1.18", group: :development, require: false
|
|
3
|
+
binstub "tomo"
|
|
4
|
+
|
|
5
|
+
say_git "Initialize the tomo config file"
|
|
6
|
+
if File.exist?(".tomo/config.rb")
|
|
7
|
+
say_status :skip, ".tomo/config.rb exists", :blue
|
|
8
|
+
else
|
|
9
|
+
bundle_command! "exec tomo init"
|
|
10
|
+
end
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
say_git "Install the vite_rails gem"
|
|
2
|
+
install_gem "vite_rails", version: "~> 3.0"
|
|
3
|
+
|
|
4
|
+
say_git "Move asset pipeline files into app/frontend"
|
|
5
|
+
remove_file "app/assets/stylesheets/application.css"
|
|
6
|
+
remove_file "app/javascript/application.js"
|
|
7
|
+
move "app/javascript", "app/frontend"
|
|
8
|
+
move "app/assets/images", "app/frontend/images"
|
|
9
|
+
move "app/assets/stylesheets", "app/frontend/stylesheets"
|
|
10
|
+
remove_dir "app/assets"
|
|
11
|
+
inject_into_class "config/application.rb", "Application", <<-RUBY
|
|
12
|
+
# Prevents Rails from trying to eager-load the contents of app/frontend
|
|
13
|
+
config.javascript_path = "frontend"
|
|
14
|
+
|
|
15
|
+
RUBY
|
|
16
|
+
|
|
17
|
+
say_git "Run the vite installer"
|
|
18
|
+
bundle_command "exec vite install"
|
|
19
|
+
gsub_file "app/views/layouts/application.html.erb",
|
|
20
|
+
/vite_javascript_tag 'application' %>/,
|
|
21
|
+
'vite_javascript_tag "application", "data-turbo-track": "reload" %>'
|
|
22
|
+
|
|
23
|
+
say_git "Replace vite-plugin-ruby with vite-plugin-rails"
|
|
24
|
+
add_yarn_packages "rollup@^3.26.3", "vite-plugin-rails@0.1.0"
|
|
25
|
+
remove_yarn_package "vite-plugin-ruby"
|
|
26
|
+
gsub_file "vite.config.ts", "import RubyPlugin from 'vite-plugin-ruby'", 'import ViteRails from "vite-plugin-rails"'
|
|
27
|
+
gsub_file "vite.config.ts", /^\s*?RubyPlugin\(\)/, <<~TYPESCRIPT.gsub(/^/, " ").rstrip
|
|
28
|
+
ViteRails({
|
|
29
|
+
envVars: { RAILS_ENV: "development" },
|
|
30
|
+
envOptions: { defineOn: "import.meta.env" },
|
|
31
|
+
fullReload: {
|
|
32
|
+
additionalPaths: [],
|
|
33
|
+
},
|
|
34
|
+
})
|
|
35
|
+
TYPESCRIPT
|
|
36
|
+
|
|
37
|
+
say_git "Move vite package from devDependencies to dependencies"
|
|
38
|
+
if (vite_version = File.read("package.json")[/"vite":\s*"(.+?)"/, 1])
|
|
39
|
+
remove_yarn_package "vite", capture: true
|
|
40
|
+
add_yarn_package "vite@#{vite_version}"
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
say_git "Install autoprefixer"
|
|
44
|
+
add_yarn_packages "postcss@^8.4.24", "autoprefixer@^10.4.14"
|
|
45
|
+
copy_file "postcss.config.js"
|
|
46
|
+
|
|
47
|
+
say_git "Disable autoBuild in test environment"
|
|
48
|
+
gsub_file "config/vite.json", /("test": \{.+?"autoBuild":\s*)true/m, '\1false'
|
|
49
|
+
copy_test_support_file "vite.rb"
|
|
50
|
+
|
|
51
|
+
say_git "Install modern-normalize and base stylesheets"
|
|
52
|
+
add_yarn_package "modern-normalize@^2.0.0"
|
|
53
|
+
copy_file "app/frontend/stylesheets/index.css"
|
|
54
|
+
copy_file "app/frontend/stylesheets/base.css"
|
|
55
|
+
copy_file "app/frontend/stylesheets/reset.css"
|
|
56
|
+
|
|
57
|
+
say_git "Configure turbo/stimulus"
|
|
58
|
+
package_json = File.read("package.json")
|
|
59
|
+
if package_json.match?(%r{@hotwired/turbo-rails})
|
|
60
|
+
prepend_to_file "app/frontend/entrypoints/application.js", <<~JS
|
|
61
|
+
import "@hotwired/turbo-rails";
|
|
62
|
+
JS
|
|
63
|
+
end
|
|
64
|
+
if package_json.match?(%r{@hotwired/stimulus})
|
|
65
|
+
add_yarn_package "stimulus-vite-helpers"
|
|
66
|
+
copy_file "app/frontend/controllers/index.js", force: true
|
|
67
|
+
prepend_to_file "app/frontend/entrypoints/application.js", <<~JS
|
|
68
|
+
import "~/controllers";
|
|
69
|
+
JS
|
|
70
|
+
end
|
|
71
|
+
prepend_to_file "app/frontend/entrypoints/application.js", <<~JS
|
|
72
|
+
import "~/stylesheets/index.css";
|
|
73
|
+
JS
|
|
74
|
+
|
|
75
|
+
say_git "Add InlineSvgHelper"
|
|
76
|
+
if File.read("config/application.rb").match?(/^\s*config\.autoload_lib/)
|
|
77
|
+
copy_file "lib/vite_inline_svg_file_loader.rb"
|
|
78
|
+
else
|
|
79
|
+
empty_directory "app/lib"
|
|
80
|
+
copy_file "lib/vite_inline_svg_file_loader.rb", "app/lib/vite_inline_svg_file_loader.rb"
|
|
81
|
+
end
|
|
82
|
+
copy_file "app/helpers/inline_svg_helper.rb"
|
|
83
|
+
copy_file "app/frontend/images/example.svg"
|
|
84
|
+
# TODO: rspec support
|
|
85
|
+
copy_file "test/helpers/inline_svg_helper_test.rb" if minitest?
|
|
86
|
+
|
|
87
|
+
say_git "Add a `yarn start` script"
|
|
88
|
+
start = "concurrently -i -k --kill-others-on-fail -p none 'RUBY_DEBUG_OPEN=true bin/rails s' 'bin/vite dev'"
|
|
89
|
+
add_package_json_script start: start
|
|
90
|
+
add_yarn_package "concurrently", dev: true
|
|
91
|
+
gsub_file "README.md", %r{bin/rails s(erver)?}, "yarn start"
|
|
92
|
+
gsub_file "bin/setup", %r{bin/rails s(erver)?}, "yarn start"
|
|
93
|
+
remove_file "Procfile.dev"
|
|
94
|
+
|
|
95
|
+
say_git "Remove jsbundling-rails"
|
|
96
|
+
remove_gem "jsbundling-rails"
|
|
97
|
+
|
|
98
|
+
say_git "Remove sprockets"
|
|
99
|
+
remove_file "config/initializers/assets.rb"
|
|
100
|
+
comment_lines "config/environments/development.rb", /^\s*config\.assets\./
|
|
101
|
+
comment_lines "config/environments/production.rb", /^\s*config\.assets\./
|
|
102
|
+
gsub_file "app/views/layouts/application.html.erb", /^.*<%= stylesheet_link_tag.*$/, ""
|
|
103
|
+
remove_gem "sprockets-rails"
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
require "yaml"
|
|
2
|
+
|
|
3
|
+
module Nextgen
|
|
4
|
+
class Generators
|
|
5
|
+
def self.compatible_with(rails_opts:)
|
|
6
|
+
yaml_path = File.expand_path("../../config/generators.yml", __dir__)
|
|
7
|
+
new.tap do |g|
|
|
8
|
+
YAML.load_file(yaml_path).each do |name, options|
|
|
9
|
+
options ||= {}
|
|
10
|
+
requirements = Array(options["requires"])
|
|
11
|
+
next unless requirements.all? { |req| rails_opts.public_send("#{req}?") }
|
|
12
|
+
|
|
13
|
+
g.add(
|
|
14
|
+
name.to_sym,
|
|
15
|
+
prompt: options["prompt"],
|
|
16
|
+
description: options["description"],
|
|
17
|
+
node: !!options["node"]
|
|
18
|
+
)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
g.deactivate_node unless rails_opts.requires_node?
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def initialize
|
|
26
|
+
@generators = {}
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def node_active?
|
|
30
|
+
!!generators.fetch(:node)[:active]
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def all_active
|
|
34
|
+
generators.each_with_object([]) do |(name, meta), result|
|
|
35
|
+
result << name if meta[:active]
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def add(name, node: false, prompt: nil, description: nil)
|
|
40
|
+
name = name.to_sym
|
|
41
|
+
raise ArgumentError, "Generator #{name.inspect} was already added" if generators.key?(name)
|
|
42
|
+
|
|
43
|
+
generators[name] = {node: node, prompt: prompt, description: description}
|
|
44
|
+
activate(name) unless prompt
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def optional
|
|
48
|
+
generators.each_with_object({}) do |(name, meta), result|
|
|
49
|
+
result[meta[:prompt]] = name if meta[:prompt]
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def activate(*optional_generators)
|
|
54
|
+
optional_generators.each do |name|
|
|
55
|
+
name = name.to_sym
|
|
56
|
+
gen = generators.fetch(name)
|
|
57
|
+
gen[:active] = true
|
|
58
|
+
activate(:node) if name != :node && gen[:node]
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def deactivate_node
|
|
63
|
+
generators.fetch(:node)[:active] = false
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def to_ruby_script
|
|
67
|
+
apply_statements = all_active.map do |generator|
|
|
68
|
+
description = generators.fetch(generator)[:description]
|
|
69
|
+
path = Nextgen.generators_path.join("#{generator}.rb")
|
|
70
|
+
"apply_as_git_commit #{path.to_s.inspect}, message: #{description.inspect}"
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
<<~SCRIPT
|
|
74
|
+
require #{File.expand_path("../nextgen", __dir__).inspect}
|
|
75
|
+
extend Nextgen::Actions
|
|
76
|
+
|
|
77
|
+
with_nextgen_source_path do
|
|
78
|
+
#{apply_statements.join("\n ")}
|
|
79
|
+
end
|
|
80
|
+
SCRIPT
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
private
|
|
84
|
+
|
|
85
|
+
attr_reader :generators
|
|
86
|
+
end
|
|
87
|
+
end
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
require "rails/version"
|
|
2
|
+
|
|
3
|
+
module Nextgen
|
|
4
|
+
module Rails
|
|
5
|
+
class << self
|
|
6
|
+
def version
|
|
7
|
+
::Rails.version
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def edge_branch
|
|
11
|
+
if version.match?(/[a-z]/i)
|
|
12
|
+
"main"
|
|
13
|
+
else
|
|
14
|
+
version[/^\d+\.\d+/].tr(".", "-") + "-stable"
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def run(*args, raise_on_error: true)
|
|
19
|
+
command = "rails", *args
|
|
20
|
+
say_status :run, *command.join(" ")
|
|
21
|
+
with_original_bundler_env do
|
|
22
|
+
system(*command, exception: raise_on_error)
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
private
|
|
27
|
+
|
|
28
|
+
def say_status(...)
|
|
29
|
+
Thor::Base.shell.new.say_status(...)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def with_original_bundler_env(&block)
|
|
33
|
+
return yield unless defined?(Bundler)
|
|
34
|
+
|
|
35
|
+
Bundler.with_original_env(&block)
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
module Nextgen
|
|
2
|
+
class RailsOptions
|
|
3
|
+
DATABASES = %w[
|
|
4
|
+
postgresql
|
|
5
|
+
mysql
|
|
6
|
+
sqlite3
|
|
7
|
+
oracle
|
|
8
|
+
sqlserver
|
|
9
|
+
jdbcmysql
|
|
10
|
+
jdbcsqlite3
|
|
11
|
+
jdbcpostgresql
|
|
12
|
+
jdbc
|
|
13
|
+
].freeze
|
|
14
|
+
|
|
15
|
+
TEST_FRAMEWORKS = %w[minitest rspec].freeze
|
|
16
|
+
|
|
17
|
+
ASSET_PIPELINES = %w[sprockets propshaft].freeze
|
|
18
|
+
|
|
19
|
+
OPTIONAL_FRAMEWORKS = %w[
|
|
20
|
+
action_mailer
|
|
21
|
+
action_mailbox
|
|
22
|
+
action_text
|
|
23
|
+
active_job
|
|
24
|
+
active_storage
|
|
25
|
+
action_cable
|
|
26
|
+
hotwire
|
|
27
|
+
jbuilder
|
|
28
|
+
].freeze
|
|
29
|
+
|
|
30
|
+
attr_reader :asset_pipeline, :css, :javascript, :database, :test_framework
|
|
31
|
+
|
|
32
|
+
def initialize
|
|
33
|
+
@api = false
|
|
34
|
+
@edge = false
|
|
35
|
+
@skip_frameworks = []
|
|
36
|
+
@skip_system_test = false
|
|
37
|
+
@test_framework = "minitest"
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def asset_pipeline=(pipeline)
|
|
41
|
+
raise ArgumentError, "Unknown asset pipeline: #{pipeline}" unless [nil, *ASSET_PIPELINES].include?(pipeline)
|
|
42
|
+
|
|
43
|
+
@asset_pipeline = pipeline
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def css=(framework)
|
|
47
|
+
raise ArgumentError, "Can't specify css in API mode" if api?
|
|
48
|
+
raise ArgumentError, "Can't specify css when asset pipeline is disabled" if skip_asset_pipeline?
|
|
49
|
+
|
|
50
|
+
@css = framework
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def javascript=(framework)
|
|
54
|
+
raise ArgumentError, "Can't specify javascript in API mode" if api? && framework
|
|
55
|
+
|
|
56
|
+
if skip_asset_pipeline? && framework != "vite"
|
|
57
|
+
raise ArgumentError, "Can't specify javascript when asset pipeline is disabled"
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
@javascript = framework
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def vite?
|
|
64
|
+
@javascript == "vite"
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def skip_javascript?
|
|
68
|
+
defined?(@javascript) && @javascript.nil?
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def database=(db)
|
|
72
|
+
raise ArgumentError, "Unknown database: #{db}" unless [nil, *DATABASES].include?(db)
|
|
73
|
+
|
|
74
|
+
@database = db
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def postgresql?
|
|
78
|
+
database == "postgresql"
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def test_framework=(framework)
|
|
82
|
+
raise ArgumentError, "Unknown test framework: #{framework}" unless [nil, *TEST_FRAMEWORKS].include?(framework)
|
|
83
|
+
|
|
84
|
+
@test_framework = framework
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def test_framework?
|
|
88
|
+
!!@test_framework
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def edge!
|
|
92
|
+
@edge = true
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def edge?
|
|
96
|
+
@edge
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def api!
|
|
100
|
+
raise ArgumentError, "Can't specify API mode if css is already specified" if css
|
|
101
|
+
raise ArgumentError, "Can't specify API mode if javascript is already specified" if javascript
|
|
102
|
+
|
|
103
|
+
@api = true
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def api?
|
|
107
|
+
@api
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def frontend?
|
|
111
|
+
!api?
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def requires_node?
|
|
115
|
+
%w[bootstrap bulma postcss sass].include?(css) || %w[webpack esbuild rollup vite].include?(javascript)
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def rspec?
|
|
119
|
+
@test_framework == "rspec"
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def active_record?
|
|
123
|
+
!skip_active_record?
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def skip_active_record?
|
|
127
|
+
defined?(@database) && @database.nil?
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
def skip_asset_pipeline?
|
|
131
|
+
defined?(@asset_pipeline) && @asset_pipeline.nil?
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
def skip_system_test!
|
|
135
|
+
@skip_system_test = true
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
def skip_system_test?
|
|
139
|
+
@skip_system_test
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
def skip_test?
|
|
143
|
+
defined?(@test_framework) && [nil, "rspec"].include?(@test_framework)
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
def system_testing?
|
|
147
|
+
!(api? || test_framework.nil? || skip_system_test?)
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
def action_mailer?
|
|
151
|
+
!skip_optional_framework?("action_mailer")
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
def active_job?
|
|
155
|
+
!skip_optional_framework?("active_job")
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
def skip_optional_framework!(framework)
|
|
159
|
+
raise ArgumentError, "Unknown framework: #{framework}" unless OPTIONAL_FRAMEWORKS.include?(framework)
|
|
160
|
+
|
|
161
|
+
skip_frameworks << framework
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
def skip_optional_framework?(framework)
|
|
165
|
+
raise ArgumentError, "Unknown framework: #{framework}" unless OPTIONAL_FRAMEWORKS.include?(framework)
|
|
166
|
+
|
|
167
|
+
skip_frameworks.include?(framework)
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
def to_args # rubocop:disable Metrics/PerceivedComplexity
|
|
171
|
+
[].tap do |args|
|
|
172
|
+
args << "--edge" if edge?
|
|
173
|
+
args << "--api" if api?
|
|
174
|
+
args << "--skip-active-record" if skip_active_record?
|
|
175
|
+
args << "--skip-asset-pipeline" if skip_asset_pipeline?
|
|
176
|
+
args << "--skip-javascript" if skip_javascript?
|
|
177
|
+
args << "--skip-test" if skip_test?
|
|
178
|
+
args << "--skip-system-test" if skip_system_test?
|
|
179
|
+
args << "--asset-pipeline=#{asset_pipeline}" if asset_pipeline
|
|
180
|
+
args << "--database=#{database}" if database
|
|
181
|
+
args << "--css=#{css}" if css
|
|
182
|
+
args << "--javascript=#{javascript}" if javascript
|
|
183
|
+
args.push(*skip_frameworks.map { "--skip-#{_1.tr("_", "-")}" })
|
|
184
|
+
end
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
private
|
|
188
|
+
|
|
189
|
+
attr_reader :skip_frameworks
|
|
190
|
+
end
|
|
191
|
+
end
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
module Nextgen
|
|
2
|
+
module ThorExtensions
|
|
3
|
+
def self.extended(base)
|
|
4
|
+
super
|
|
5
|
+
base.check_unknown_options!
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def start(given_args = ARGV, config = {})
|
|
9
|
+
config[:shell] ||= Thor::Base.shell.new
|
|
10
|
+
handle_help_switches(given_args) do |args|
|
|
11
|
+
dispatch(nil, args, nil, config)
|
|
12
|
+
end
|
|
13
|
+
rescue Exception => e # rubocop:disable Lint/RescueException
|
|
14
|
+
handle_exception_on_start(e, config)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
private
|
|
18
|
+
|
|
19
|
+
def handle_help_switches(given_args)
|
|
20
|
+
yield(given_args.dup)
|
|
21
|
+
rescue Thor::UnknownArgumentError => e
|
|
22
|
+
retry_with_args = []
|
|
23
|
+
|
|
24
|
+
if given_args.first == "help"
|
|
25
|
+
retry_with_args = ["help"] if given_args.length > 1
|
|
26
|
+
elsif (e.unknown & %w[-h --help]).any?
|
|
27
|
+
retry_with_args = ["help", (given_args - e.unknown).first]
|
|
28
|
+
end
|
|
29
|
+
raise unless retry_with_args.any?
|
|
30
|
+
|
|
31
|
+
yield(retry_with_args)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def handle_exception_on_start(error, config)
|
|
35
|
+
case error
|
|
36
|
+
when Errno::EPIPE
|
|
37
|
+
# Ignore
|
|
38
|
+
when Thor::Error, Interrupt
|
|
39
|
+
raise unless config.fetch(:exit_on_failure, true)
|
|
40
|
+
|
|
41
|
+
config[:shell]&.say_error(error.message, :red)
|
|
42
|
+
exit(false)
|
|
43
|
+
else
|
|
44
|
+
raise
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
module Nextgen
|
|
2
|
+
class TidyGemfile
|
|
3
|
+
def self.clean!(path = "Gemfile")
|
|
4
|
+
gemfile = new(path)
|
|
5
|
+
gemfile.clean
|
|
6
|
+
gemfile.save
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def initialize(path = "Gemfile")
|
|
10
|
+
@path = path
|
|
11
|
+
@gemfile = File.read(path)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def include?(gem)
|
|
15
|
+
gemfile.match?(/^\s*gem\s+['"]#{gem}['"]/)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def clean
|
|
19
|
+
@gemfile = gemfile
|
|
20
|
+
.gsub(/^\s*#.*/, "") # remove comments
|
|
21
|
+
.gsub(/(\s*\n)+/, "\n") # remove blank lines
|
|
22
|
+
.gsub(/^(ruby.*)/, "\n\\1\n") # ensure blank space around "ruby" line
|
|
23
|
+
.gsub(/^(group.*)/, "\n\\1") # ensure blank space before each "group" block
|
|
24
|
+
nil
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def add(gem, version: nil, group: nil, require: nil)
|
|
28
|
+
return false if include?(gem)
|
|
29
|
+
|
|
30
|
+
gem_line = build_gem_line(gem, version: version, require: require, indent: group ? " " : "")
|
|
31
|
+
|
|
32
|
+
if group
|
|
33
|
+
group_line = create_group_if_needed(group)
|
|
34
|
+
gemfile.sub!(/#{Regexp.quote(group_line)}/, '\0' + gem_line)
|
|
35
|
+
else
|
|
36
|
+
gemfile.sub!(/^(#|gem\s)/, gem_line + '\0')
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# Add a blank line after the gem if the subsequent line starts with a comment
|
|
40
|
+
gemfile.sub!(/(#{Regexp.quote(gem_line)})(\s*#)/, "\\1\n\\2")
|
|
41
|
+
true
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def remove(gem)
|
|
45
|
+
!!gemfile.gsub!(/^( *#.*?\n)?\s*gem\s+['"]#{gem}['"].*\n/, "")
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def save
|
|
49
|
+
File.write(@path, gemfile.rstrip + "\n")
|
|
50
|
+
true
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
private
|
|
54
|
+
|
|
55
|
+
attr_reader :gemfile
|
|
56
|
+
|
|
57
|
+
def create_group_if_needed(group)
|
|
58
|
+
group_line = "group " + Array(group).map(&:inspect).join(", ") + " do\n"
|
|
59
|
+
gemfile << "\n#{group_line}end\n" unless gemfile.include?(group_line)
|
|
60
|
+
group_line
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def build_gem_line(gem, version:, require:, indent:)
|
|
64
|
+
line = %(gem "#{gem}")
|
|
65
|
+
line << ", #{version.to_s.inspect}" if version
|
|
66
|
+
line << ", require: #{require.inspect}" unless require.nil?
|
|
67
|
+
|
|
68
|
+
indent + line + "\n"
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
data/lib/nextgen.rb
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
require "pathname"
|
|
2
|
+
require "zeitwerk"
|
|
3
|
+
|
|
4
|
+
loader = Zeitwerk::Loader.for_gem
|
|
5
|
+
loader.ignore("#{__dir__}/nextgen/generators")
|
|
6
|
+
loader.inflector.inflect("cli" => "CLI")
|
|
7
|
+
loader.setup
|
|
8
|
+
|
|
9
|
+
module Nextgen
|
|
10
|
+
def self.generators_path
|
|
11
|
+
Pathname.new(__dir__).join("nextgen/generators")
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def self.template_path
|
|
15
|
+
Pathname.new(__dir__).join("../template")
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
---
|
|
2
|
+
EnableDefaultLinters: true
|
|
3
|
+
exclude:
|
|
4
|
+
- "node_modules/**/*"
|
|
5
|
+
- "vendor/**/*"
|
|
6
|
+
linters:
|
|
7
|
+
ErbSafety:
|
|
8
|
+
enabled: true
|
|
9
|
+
<% if File.exist?(".rubocop.yml") -%>
|
|
10
|
+
Rubocop:
|
|
11
|
+
enabled: true
|
|
12
|
+
rubocop_config:
|
|
13
|
+
inherit_from:
|
|
14
|
+
- .rubocop.yml
|
|
15
|
+
Layout/InitialIndentation:
|
|
16
|
+
Enabled: false
|
|
17
|
+
Layout/TrailingEmptyLines:
|
|
18
|
+
Enabled: false
|
|
19
|
+
Lint/UselessAssignment:
|
|
20
|
+
Enabled: false
|
|
21
|
+
Naming/FileName:
|
|
22
|
+
Enabled: false
|
|
23
|
+
Rails/OutputSafety:
|
|
24
|
+
Enabled: false
|
|
25
|
+
<% end -%>
|