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