nextgen 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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 -%>