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.
Files changed (102) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE.txt +21 -0
  3. data/README.md +143 -0
  4. data/config/generators.yml +146 -0
  5. data/exe/nextgen +13 -0
  6. data/lib/nextgen/actions/bundler.rb +62 -0
  7. data/lib/nextgen/actions/git.rb +56 -0
  8. data/lib/nextgen/actions/yarn.rb +32 -0
  9. data/lib/nextgen/actions.rb +155 -0
  10. data/lib/nextgen/cli.rb +19 -0
  11. data/lib/nextgen/commands/create.rb +312 -0
  12. data/lib/nextgen/ext/prompt/list.rb +13 -0
  13. data/lib/nextgen/ext/prompt/multilist.rb +16 -0
  14. data/lib/nextgen/generators/action_mailer.rb +26 -0
  15. data/lib/nextgen/generators/annotate.rb +2 -0
  16. data/lib/nextgen/generators/base.rb +60 -0
  17. data/lib/nextgen/generators/basic_auth.rb +5 -0
  18. data/lib/nextgen/generators/brakeman.rb +2 -0
  19. data/lib/nextgen/generators/bundler_audit.rb +2 -0
  20. data/lib/nextgen/generators/capybara_lockstep.rb +4 -0
  21. data/lib/nextgen/generators/clean_gemfile.rb +1 -0
  22. data/lib/nextgen/generators/dotenv.rb +3 -0
  23. data/lib/nextgen/generators/erb_lint.rb +11 -0
  24. data/lib/nextgen/generators/eslint.rb +24 -0
  25. data/lib/nextgen/generators/factory_bot_rails.rb +16 -0
  26. data/lib/nextgen/generators/git_safe.rb +4 -0
  27. data/lib/nextgen/generators/github_actions.rb +2 -0
  28. data/lib/nextgen/generators/good_migrations.rb +1 -0
  29. data/lib/nextgen/generators/home_controller.rb +3 -0
  30. data/lib/nextgen/generators/initial_git_commit.rb +5 -0
  31. data/lib/nextgen/generators/initial_migrations.rb +11 -0
  32. data/lib/nextgen/generators/letter_opener.rb +8 -0
  33. data/lib/nextgen/generators/node.rb +5 -0
  34. data/lib/nextgen/generators/open_browser_on_start.rb +12 -0
  35. data/lib/nextgen/generators/overcommit.rb +1 -0
  36. data/lib/nextgen/generators/pgcli_rails.rb +1 -0
  37. data/lib/nextgen/generators/rack_canonical_host.rb +8 -0
  38. data/lib/nextgen/generators/rack_mini_profiler.rb +2 -0
  39. data/lib/nextgen/generators/rspec_rails.rb +19 -0
  40. data/lib/nextgen/generators/rspec_system_testing.rb +5 -0
  41. data/lib/nextgen/generators/rubocop.rb +32 -0
  42. data/lib/nextgen/generators/shoulda.rb +6 -0
  43. data/lib/nextgen/generators/sidekiq.rb +30 -0
  44. data/lib/nextgen/generators/stylelint.rb +24 -0
  45. data/lib/nextgen/generators/thor.rb +2 -0
  46. data/lib/nextgen/generators/tomo.rb +10 -0
  47. data/lib/nextgen/generators/vite.rb +103 -0
  48. data/lib/nextgen/generators.rb +87 -0
  49. data/lib/nextgen/rails.rb +39 -0
  50. data/lib/nextgen/rails_options.rb +191 -0
  51. data/lib/nextgen/thor_extensions.rb +48 -0
  52. data/lib/nextgen/tidy_gemfile.rb +71 -0
  53. data/lib/nextgen/version.rb +3 -0
  54. data/lib/nextgen.rb +17 -0
  55. data/template/.editorconfig +14 -0
  56. data/template/.env.sample +2 -0
  57. data/template/.erb-lint.yml.tt +25 -0
  58. data/template/.eslintrc.cjs +26 -0
  59. data/template/.github/workflows/ci.yml.tt +142 -0
  60. data/template/.overcommit.yml.tt +86 -0
  61. data/template/.prettierrc.cjs +6 -0
  62. data/template/.rubocop.yml.tt +269 -0
  63. data/template/.stylelintrc.cjs +52 -0
  64. data/template/DEPLOYMENT.md +10 -0
  65. data/template/Procfile.tt +4 -0
  66. data/template/README.md.tt +52 -0
  67. data/template/Thorfile +7 -0
  68. data/template/app/controllers/concerns/basic_auth.rb +20 -0
  69. data/template/app/controllers/home_controller.rb +4 -0
  70. data/template/app/frontend/controllers/index.js +5 -0
  71. data/template/app/frontend/images/example.svg +3 -0
  72. data/template/app/frontend/stylesheets/base.css +8 -0
  73. data/template/app/frontend/stylesheets/index.css +3 -0
  74. data/template/app/frontend/stylesheets/reset.css +36 -0
  75. data/template/app/helpers/inline_svg_helper.rb +9 -0
  76. data/template/app/views/home/index.html.erb.tt +5 -0
  77. data/template/bin/setup +107 -0
  78. data/template/config/initializers/generators.rb +5 -0
  79. data/template/config/initializers/rack_mini_profiler.rb +4 -0
  80. data/template/config/initializers/sidekiq.rb +32 -0
  81. data/template/config/sidekiq.yml +5 -0
  82. data/template/lib/puma/plugin/open.rb +14 -0
  83. data/template/lib/tasks/auto_annotate_models.rake +52 -0
  84. data/template/lib/tasks/erblint.rake +11 -0
  85. data/template/lib/tasks/eslint.rake +11 -0
  86. data/template/lib/tasks/rubocop.rake +4 -0
  87. data/template/lib/tasks/stylelint.rake +11 -0
  88. data/template/lib/templates/rspec/system/system_spec.rb +10 -0
  89. data/template/lib/vite_inline_svg_file_loader.rb +25 -0
  90. data/template/package.json +6 -0
  91. data/template/postcss.config.js +3 -0
  92. data/template/spec/support/factory_bot.rb +3 -0
  93. data/template/spec/support/mailer.rb +5 -0
  94. data/template/spec/support/shoulda.rb +6 -0
  95. data/template/spec/support/system.rb +17 -0
  96. data/template/test/application_system_test_case.rb +18 -0
  97. data/template/test/helpers/inline_svg_helper_test.rb +23 -0
  98. data/template/test/support/factory_bot.rb +3 -0
  99. data/template/test/support/mailer.rb +3 -0
  100. data/template/test/support/shoulda.rb +6 -0
  101. data/template/test/support/vite.rb +5 -0
  102. metadata +220 -0
@@ -0,0 +1,6 @@
1
+ say_git "Install shoulda gems"
2
+ install_gem "shoulda-context", group: :test if minitest?
3
+ install_gem "shoulda-matchers", group: :test
4
+
5
+ say_git "Include shoulda methods in tests"
6
+ copy_test_support_file "shoulda.rb"
@@ -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,2 @@
1
+ copy_file "Thorfile"
2
+ binstub "thor"
@@ -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
@@ -0,0 +1,3 @@
1
+ module Nextgen
2
+ VERSION = "0.1.0".freeze
3
+ 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,14 @@
1
+ # https://editorconfig.org
2
+
3
+ root = true
4
+
5
+ [*]
6
+ charset = utf-8
7
+ indent_style = space
8
+ indent_size = 2
9
+ end_of_line = lf
10
+ insert_final_newline = true
11
+ trim_trailing_whitespace = true
12
+
13
+ [Makefile]
14
+ indent_style = tab
@@ -0,0 +1,2 @@
1
+ # These environment variables are needed to run the app locally.
2
+ # Copy these into a file named .env to have them loaded automatically.
@@ -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 -%>