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
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: b5b46f28e5dece5c8f9e57335f0bd0d1d6093011d26d0fb7349a5457a47ae84d
4
+ data.tar.gz: 999357e7460f394bbb8131e70066064533b6235739104065bba22e2298d60646
5
+ SHA512:
6
+ metadata.gz: 5ceb53aceb86cb606641e8ef7c2e22678a968aa4fe45c1997673122e7f3c14cdda6ab6975d48df3b765ba300954664098fc3cc89f386fb7c1ab6f36aebb16790
7
+ data.tar.gz: 9025c715d847630295d952c54c1827b89c012df9f7fc42b22c69ee2fc3708e836acb08f877eca821ed542cafab39b49ce16bc9dabc927b503d75f383cc02a5d7
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2023 Matt Brictson
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,143 @@
1
+ # Nextgen
2
+
3
+ [![Gem Version](https://img.shields.io/gem/v/nextgen)](https://rubygems.org/gems/nextgen)
4
+ [![Gem Downloads](https://img.shields.io/gem/dt/nextgen)](https://www.ruby-toolbox.com/projects/nextgen)
5
+ [![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/mattbrictson/nextgen/ci.yml)](https://github.com/mattbrictson/nextgen/actions/workflows/ci.yml)
6
+ [![Code Climate maintainability](https://img.shields.io/codeclimate/maintainability/mattbrictson/nextgen)](https://codeclimate.com/github/mattbrictson/nextgen)
7
+
8
+ Nextgen is an **interactive** and flexible alternative to `rails new` that includes opt-in support for modern frontend development with ⚡️**Vite.**
9
+
10
+ [**About**](#about)
11
+ |
12
+ [**Requirements**](#requirements)
13
+ |
14
+ [**Usage**](#usage)
15
+ |
16
+ [**What's included**](#whats-included)<br>
17
+ [Support](#support)
18
+ |
19
+ [License](#license)
20
+ |
21
+ [Code of conduct](#code-of-conduct)
22
+ |
23
+ [Contribution guide](#contribution-guide)
24
+
25
+ TODO: screen recording goes here
26
+
27
+ ## About
28
+
29
+ Nextgen is an interactive and flexible Rails app generator that starts with the official recommendations of `rails new`, and then lets you apply dozens of best-practices, tweaks, documentation, and personal preferences. Opt out of things you don't need (Action Mailbox?), choose your test framework and database, and opt into a curated set of additional gems like FactoryBot and RuboCop configured with reasonable defaults.
30
+
31
+ If you need to use React or (gasp!) TypeScript alongside Rails, Nextgen also has you covered with its ability to generate a robust Vite + Rails setup. [Vite](https://vite-ruby.netlify.app) brings excellent frontend tooling that goes well beyond what the default Rails stack can provide.
32
+
33
+ Nextgen is maintained by [@mattbrictson](https://github.com/mattbrictson) and is the tool I use to generate all of my personal and professional Rails projects. For more details, check out [what's included](#whats-included) below, or peruse the [examples directory](./examples) of apps generated by Nextgen.
34
+
35
+ ## Requirements
36
+
37
+ Nextgen generates apps using **Rails 7.1**.
38
+
39
+ - **Ruby 3.0+** is required
40
+ - **Rubygems 3.4.8+** is required (run `gem update --system` to get it)
41
+ - **Node 18+ and Yarn** are required if you choose Vite or other Node-based options
42
+ - Additional tools may be required depending on the options you select (e.g. PostgreSQL)
43
+
44
+ Going forward, my goal is that Nextgen will always target the latest stable version of Rails and the next pre-release version. Support for Node LTS and Ruby versions will be dropped as soon as they reach EOL (see [Ruby](https://endoflife.date/ruby) and [Node](https://endoflife.date/nodejs) EOL schedules).
45
+
46
+ ## Usage
47
+
48
+ To create a new Rails app with Nextgen, run:
49
+
50
+ ```
51
+ gem exec nextgen create myapp
52
+ ```
53
+
54
+ This will download the latest version of the `nextgen` gem and use it to create an app in the `myapp` directory. You'll be asked to configure the tech stack through several interactive prompts. If you have a `~/.railsrc` file, it will be ignored.
55
+
56
+ > [!NOTE]
57
+ > If you get an "Unknown command exec" error, fix it by upgrading rubygems: `gem update --system`.
58
+
59
+ ## What's included
60
+
61
+ **Nextgen starts with the "omakase" default behavior of `rails new`,** so you get the great things included in Rails 7.1 like a production-ready Dockerfile, your choice of database platform, CSS framework, etc. You can also interactively disable parts of the default stack that you don't need, like JBuilder or Action Mailbox.
62
+
63
+ On top of that foundation, Nextgen offers dozens of useful enhancements to the vanilla Rails experience. You are free to pick and choose which (if any) of these to apply to your new project. Behind the scenes, **each enhancement is applied in a separate git commit,** so that you can later see what was applied and why, and revert the suggestions if necessary.
64
+
65
+ > [!NOTE]
66
+ > For the full list of what Nextgen provides, check out [config/generators.yml](https://github.com/mattbrictson/nextgen/tree/main/config/generators.yml). The source code of each generator can be found in [lib/nextgen/generators](https://github.com/mattbrictson/nextgen/tree/main/lib/nextgen/generators).
67
+
68
+ Here are some highlights of what Nextgen brings to the table:
69
+
70
+ ### GitHub Actions
71
+
72
+ Nextgen can optionally set up a GitHub Actions CI workflow for your app that automatically runs tests, linters, and security checks based on the gems and packages you have installed.
73
+
74
+ ### Minitest or RSpec
75
+
76
+ Prefer RSpec? Nextgen can set you up with RSpec, plus the gems and configuration you need for system specs (browser testing). Or stick with the Rails Minitest defaults. In either case, Nextgen will set up a good default Rake task and appropriate CI job.
77
+
78
+ ### Gems
79
+
80
+ Nextgen can install and configure your choice of these recommended gems:
81
+
82
+ - [annotate](https://github.com/ctran/annotate_models)
83
+ - [brakeman](https://github.com/presidentbeef/brakeman)
84
+ - [bundler-audit](https://github.com/rubysec/bundler-audit)
85
+ - [capybara-lockstep](https://github.com/makandra/capybara-lockstep)
86
+ - [dotenv-rails](https://github.com/bkeepers/dotenv)
87
+ - [erb_lint](https://github.com/Shopify/erb-lint)
88
+ - [factory_bot_rails](https://github.com/thoughtbot/factory_bot_rails)
89
+ - [good_migrations](https://github.com/testdouble/good-migrations)
90
+ - [letter_opener](https://github.com/ryanb/letter_opener)
91
+ - [overcommit](https://github.com/sds/overcommit)
92
+ - [pgcli-rails](https://github.com/mattbrictson/pgcli-rails)
93
+ - [rack-canonical-host](https://github.com/tylerhunt/rack-canonical-host)
94
+ - [rack-mini-profiler](https://github.com/MiniProfiler/rack-mini-profiler)
95
+ - [rubocop](https://github.com/rubocop/rubocop)
96
+ - [shoulda-matchers](https://github.com/thoughtbot/shoulda-matchers)
97
+ - [sidekiq](https://github.com/sidekiq/sidekiq)
98
+ - [thor](https://github.com/rails/thor)
99
+ - [tomo](https://github.com/mattbrictson/tomo)
100
+
101
+ ### Node packages
102
+
103
+ Nextgen can optionally install and configure these Node packages to work nicely with Rails:
104
+
105
+ - [eslint](https://github.com/eslint/eslint)
106
+ - [stylelint](https://github.com/stylelint/stylelint)
107
+
108
+ ### Vite
109
+
110
+ [Vite](https://vitejs.dev) (pronounced _veet_) is a next generation Node-based frontend build system and hot-reloading dev server. It can completely take the place of the asset pipeline and integrate seamlessly with Rails via the [vite_rails](https://github.com/ElMassimo/vite_ruby) gem. If you opt-in, Nextgen can set you up with Vite and adds a bunch of vite_rails best practices:
111
+
112
+ - All frontend sources (CSS, JS, images) are moved to `app/frontend`
113
+ - A `yarn start` script is used to start the Rails server and Vite dev server
114
+ - The [autoprefixer](https://github.com/postcss/autoprefixer) package is installed and activated via PostCSS
115
+ - A base set of CSS files are added, including [modern_normalize](https://github.com/sindresorhus/modern-normalize)
116
+ - A Vite-compatible inline SVG helper is added
117
+ - The [stimulus-vite-helpers](https://github.com/ElMassimo/stimulus-vite-helpers) package is installed (if Stimulus is detected)
118
+ - `vite-plugin-ruby` is replaced with the more full-featured `vite-plugin-rails`
119
+ - Sprockets is removed and the asset pipeline is disabled
120
+ - Vite's `autoBuild` is turned off for the test environment
121
+
122
+ These recommendations are based on my experience using Vite with Rails in multiple production apps. For additional background on these and other Vite-related changes, check out the following blog posts:
123
+
124
+ - [How to organize CSS in a Rails project](https://mattbrictson.com/blog/organizing-css-in-rails)
125
+ - [Fixing slow, flaky system tests in Vite-Rails](https://mattbrictson.com/blog/faster-vite-test-without-autobuild)
126
+ - [The 3 Vite plugins I use on every new Rails project](https://mattbrictson.com/blog/3-vite-rails-plugins)
127
+ - [Inline SVGs with Rails and Vite](https://mattbrictson.com/blog/inline-svg-with-vite-rails)
128
+
129
+ ## Support
130
+
131
+ If you want to report a bug, or have ideas, feedback or questions about Nextgen, [let me know via GitHub issues](https://github.com/mattbrictson/nextgen/issues/new) and I will do my best to provide a helpful answer. Happy hacking!
132
+
133
+ ## License
134
+
135
+ Nextgen is available as open source under the terms of the [MIT License](LICENSE.txt).
136
+
137
+ ## Code of conduct
138
+
139
+ Everyone interacting in this project’s codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](CODE_OF_CONDUCT.md).
140
+
141
+ ## Contribution guide
142
+
143
+ Pull requests are welcome!
@@ -0,0 +1,146 @@
1
+ initial_git_commit:
2
+
3
+ base:
4
+ description: "Enhance base Rails template with better docs, etc"
5
+
6
+ initial_migrations:
7
+ description: "Install and run initial migrations"
8
+ requires: active_record
9
+
10
+ clean_gemfile:
11
+ description: "Clean up Gemfile"
12
+
13
+ rspec_rails:
14
+ description: "Install and configure rspec-rails"
15
+ requires: rspec
16
+
17
+ rspec_system_testing:
18
+ description: "Install capybara + selenium-webdriver and set up system specs"
19
+ requires:
20
+ - rspec
21
+ - system_testing
22
+
23
+ node:
24
+ description: "Set up Node and Yarn"
25
+
26
+ vite:
27
+ description: "Replace the asset pipeline with Vite in app/frontend"
28
+ requires: vite
29
+ node: true
30
+
31
+ action_mailer:
32
+ description: "Improve Action Mailer support for absolute URLs and testing"
33
+ requires: action_mailer
34
+
35
+ annotate:
36
+ prompt: "Annotate Models"
37
+ description: "Install annotate gem to auto-generate schema annotations"
38
+ requires: active_record
39
+
40
+ basic_auth:
41
+ prompt: "BasicAuth controller concern"
42
+ description: "Allow app to be secured with ENV-based basic auth credentials"
43
+
44
+ brakeman:
45
+ prompt: "Brakeman"
46
+ description: "Install brakeman gem for security checks"
47
+
48
+ bundler_audit:
49
+ prompt: "Bundler Audit"
50
+ description: "Install bundler-audit gem to detect CVEs in Ruby dependencies"
51
+
52
+ capybara_lockstep:
53
+ prompt: "capybara-lockstep"
54
+ description: "Install capybara-lockstep gem for less-flaky browser testing"
55
+ requires: system_testing
56
+
57
+ dotenv:
58
+ prompt: "dotenv-rails"
59
+ description: "Install dotenv-rails gem and add .env.sample"
60
+
61
+ erb_lint:
62
+ prompt: "ERB Lint"
63
+ description: "Install erb_lint gem and correct existing issues"
64
+ requires: frontend
65
+
66
+ eslint:
67
+ prompt: "ESLint"
68
+ description: "Install eslint + supporting packages; apply prettier format"
69
+ requires: frontend
70
+ node: true
71
+
72
+ factory_bot_rails:
73
+ prompt: "Factory Bot"
74
+ description: "Install and configure factory_bot_rails gem"
75
+ requires: active_record
76
+
77
+ git_safe:
78
+
79
+ good_migrations:
80
+ prompt: "good_migrations"
81
+ description: "Install good_migrations gem"
82
+ requires: active_record
83
+
84
+ home_controller:
85
+ description: "Create a controller, view, and route for the home page"
86
+ requires: frontend
87
+
88
+ letter_opener:
89
+ prompt: "letter_opener"
90
+ description: "Install letter_opener gem to use with Action Mailer in dev"
91
+ requires: action_mailer
92
+
93
+ open_browser_on_start:
94
+ prompt: "Open browser on startup"
95
+ description: "Configure puma to launch browser on startup in development"
96
+ requires: frontend
97
+
98
+ pgcli_rails:
99
+ prompt: "pgcli_rails"
100
+ description: "Install pgcli_rails gem to allow easy use of pgcli"
101
+ requires: postgresql
102
+
103
+ rack_canonical_host:
104
+ prompt: "rack-canonical-host"
105
+ description: "Install rack-canonical-host gem; use RAILS_HOSTNAME"
106
+
107
+ rack_mini_profiler:
108
+ prompt: "rack-mini-profiler"
109
+ description: "Install rack-mini-profiler gem in development"
110
+ requires: frontend
111
+
112
+ shoulda:
113
+ prompt: "shoulda"
114
+ description: "Install shoulda-matchers gem for concise model testing"
115
+ requires: test_framework
116
+
117
+ sidekiq:
118
+ prompt: "Sidekiq"
119
+ description: "Install sidekiq gem to use in production"
120
+ requires: active_job
121
+
122
+ stylelint:
123
+ prompt: "Stylelint"
124
+ description: "Install stylelint and apply prettier format to CSS"
125
+ requires: frontend
126
+ node: true
127
+
128
+ thor:
129
+ prompt: "Thor"
130
+ description: "Configure Thor for ease of writing Rails tasks"
131
+
132
+ tomo:
133
+ prompt: "Tomo"
134
+ description: "Install tomo gem for SSH-based deployment"
135
+
136
+ rubocop:
137
+ prompt: "RuboCop"
138
+ description: "Install rubocop gems; apply formatting rules"
139
+
140
+ overcommit:
141
+ prompt: "Overcommit"
142
+ description: "Configure overcommit pre-commit git hooks"
143
+
144
+ github_actions:
145
+ prompt: "GitHub Actions"
146
+ description: "Configure GitHub Actions workflow for CI"
data/exe/nextgen ADDED
@@ -0,0 +1,13 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "nextgen"
4
+
5
+ # Work around an "unresolved or ambiguous specs" warning when CLI is run via `gem exec`
6
+ # https://github.com/rubygems/rubygems/issues/6914
7
+ at_exit do
8
+ Gem.finish_resolve
9
+ rescue Exception # rubocop:disable Lint/RescueException
10
+ # ignore
11
+ end
12
+
13
+ Nextgen::CLI.start
@@ -0,0 +1,62 @@
1
+ module Nextgen
2
+ module Actions::Bundler
3
+ def binstubs(*gems)
4
+ bundle_command! "binstubs #{gems.join(" ")} --force"
5
+ end
6
+ alias binstub binstubs
7
+
8
+ def install_gems(*names, version: nil, group: nil, require: nil)
9
+ gemfile = TidyGemfile.new
10
+ inserted = names.filter do |name|
11
+ if gemfile.include?(name)
12
+ say_status :exist, name, :blue
13
+ false
14
+ else
15
+ say_status :gemfile, [name, version].compact.join(", ")
16
+ gemfile.add(name, version: version, group: group, require: require)
17
+ true
18
+ end
19
+ end
20
+ return if inserted.empty?
21
+
22
+ gemfile.save
23
+ cmd = "install"
24
+ cmd << " --quiet" if noisy_bundler_version?
25
+ bundle_command! cmd
26
+ end
27
+ alias install_gem install_gems
28
+
29
+ def remove_gem(name)
30
+ gemfile = TidyGemfile.new
31
+ return unless gemfile.include?(name)
32
+
33
+ say_status :gemfile, "remove #{name}"
34
+ gemfile.remove(name)
35
+ gemfile.save
36
+
37
+ bundle_command! "install", capture: true, verbose: false
38
+ end
39
+
40
+ # Similar to from Rails::Generators::AppBase#bundle_command, but raises if the command fails
41
+ def bundle_command!(cmd, verbose: true, capture: false)
42
+ bundler = Gem.bin_path("bundler", "bundle")
43
+ full_command = %("#{Gem.ruby}" "#{bundler}" #{cmd})
44
+
45
+ Bundler.with_original_env do
46
+ say_status :bundle, cmd, :green if verbose
47
+ run! full_command, env: {"BUNDLE_IGNORE_MESSAGES" => "1"}, verbose: false, capture: capture
48
+ end
49
+ end
50
+
51
+ def bundler_version_satisifed?(spec)
52
+ require "bundler"
53
+ Gem::Requirement.new(spec).satisfied_by?(Gem::Version.new(Bundler::VERSION))
54
+ rescue LoadError
55
+ false
56
+ end
57
+
58
+ def noisy_bundler_version?
59
+ bundler_version_satisifed?("< 2.4.17")
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,56 @@
1
+ require "open3"
2
+ require "securerandom"
3
+ require "shellwords"
4
+ require "tmpdir"
5
+
6
+ module Nextgen
7
+ module Actions::Git
8
+ def say_git(message)
9
+ @commit_messages << message if @commit_messages
10
+ say message, :cyan
11
+ end
12
+
13
+ def apply_as_git_commit(path, message: nil)
14
+ say message, :cyan if message
15
+ @commit_messages = []
16
+ initially_clean = (git_working? && git_status == :clean)
17
+ say_status :apply, File.basename(path)
18
+ apply path, verbose: false
19
+ return if !initially_clean || git_status == :clean
20
+
21
+ commit = message&.dup || "Apply #{File.basename(path)} generator from nextgen"
22
+ commit << ("\n\n- " + @commit_messages.join("\n- ")) unless @commit_messages.empty?
23
+ git_commit_all(commit)
24
+ ensure
25
+ @commit_messages = nil
26
+ end
27
+
28
+ def git_commit_all(msg)
29
+ tmp_file = File.join(Dir.tmpdir, "nextgen_git_message_#{SecureRandom.hex(8)}.rb")
30
+ File.write(tmp_file, msg.rstrip + "\n")
31
+
32
+ git add: ".", commit: "-F #{tmp_file.shellescape}"
33
+ end
34
+
35
+ def git_working?
36
+ return @git_working if defined?(@git_working)
37
+
38
+ @git_working = (git_status != :error && git_user_configured?)
39
+ end
40
+
41
+ def git_user_configured?
42
+ out, status = Open3.capture2e("git config -l")
43
+ status.success? && out.match?(/^user\.name=/) && out.match?(/^user\.email=/)
44
+ end
45
+
46
+ # Returns :clean, :dirty, or :error
47
+ def git_status
48
+ return :error unless Dir.exist?(".git")
49
+
50
+ output, status = Open3.capture2e("git status --porcelain")
51
+ return :error unless status.success?
52
+
53
+ output.empty? ? :clean : :dirty
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,32 @@
1
+ module Nextgen
2
+ module Actions::Yarn
3
+ def add_yarn_packages(*packages, dev: false)
4
+ add = dev ? "add -D" : "add"
5
+ yarn_command "#{add} #{packages.map(&:shellescape).join(" ")}"
6
+ end
7
+ alias add_yarn_package add_yarn_packages
8
+
9
+ def remove_yarn_packages(*packages, capture: false)
10
+ yarn_command "remove #{packages.map(&:shellescape).join(" ")}", capture: capture
11
+ end
12
+ alias remove_yarn_package remove_yarn_packages
13
+
14
+ def add_package_json_scripts(scripts)
15
+ scripts.each do |name, script|
16
+ cmd = "npm pkg set scripts.#{name.to_s.shellescape}=#{script.shellescape}"
17
+ say_status :run, cmd.truncate(60), :green
18
+ run! cmd, verbose: false
19
+ end
20
+ end
21
+ alias add_package_json_script add_package_json_scripts
22
+
23
+ def yarn_command(cmd, capture: false)
24
+ say_status :yarn, cmd, :green
25
+ output = run! "yarn #{cmd}", capture: true, verbose: false
26
+ return output if capture
27
+ return puts(output) unless output.match?(/^success /)
28
+
29
+ puts output.lines.grep(/^(warning|success) /).join
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,155 @@
1
+ module Nextgen
2
+ module Actions
3
+ include Bundler
4
+ include Git
5
+ include Yarn
6
+
7
+ def with_nextgen_source_path
8
+ path = Nextgen.template_path.to_s
9
+ source_paths.unshift(path)
10
+ yield
11
+ ensure
12
+ source_paths.shift if source_paths[0] == path
13
+ end
14
+
15
+ # Like Thor's built-in `run`, but raises a descriptive exception if the command fails.
16
+ def run!(cmd, env: {}, verbose: true, capture: false)
17
+ if capture
18
+ say_status :run, cmd, :green if verbose
19
+ return if options[:pretend]
20
+
21
+ require "open3"
22
+ result, status = Open3.capture2e(env, cmd)
23
+ success = status.success?
24
+ else
25
+ result = success = run(cmd, env: env, verbose: verbose)
26
+ end
27
+
28
+ return result if success
29
+
30
+ say result if result.present?
31
+ die "Failed to run command. Cannot continue. (#{cmd})"
32
+ end
33
+
34
+ def move(from, to)
35
+ if File.exist?(to)
36
+ say_status :skip, "#{to} exists", :yellow
37
+ return
38
+ end
39
+ unless File.exist?(from)
40
+ say_status :skip, "#{from} does not exit", :yellow
41
+ return
42
+ end
43
+
44
+ say_status :move, [from, to].join(" → "), :green
45
+ FileUtils.mv(from, to)
46
+ end
47
+
48
+ def minitest?
49
+ File.exist?("test/test_helper.rb")
50
+ end
51
+
52
+ def rspec?
53
+ File.exist?("spec/spec_helper.rb")
54
+ end
55
+
56
+ def copy_test_support_file(file)
57
+ if minitest?
58
+ create_test_support_directory
59
+ copy_file "test/support/#{file}"
60
+ elsif rspec?
61
+ empty_directory "spec/support"
62
+ spec_file_path = "spec/support/#{file}"
63
+ if Nextgen.template_path.join(spec_file_path).exist?
64
+ copy_file spec_file_path
65
+ else
66
+ copy_file "test/support/#{file}", spec_file_path
67
+ end
68
+ end
69
+ end
70
+
71
+ def create_test_support_directory
72
+ empty_directory "test/support"
73
+ return if File.exist?("test/test_helper.rb") && File.read("test/test_helper.rb").include?("support/**/*.rb")
74
+
75
+ append_to_file "test/test_helper.rb", <<~RUBY
76
+
77
+ Dir[File.expand_path("support/**/*.rb", __dir__)].sort.each { |rb| require(rb) }
78
+ RUBY
79
+ end
80
+
81
+ def document_deploy_var(var_name, desc = nil, required: false, default: nil)
82
+ insertion = "`#{var_name}`"
83
+ insertion << " - #{desc}" if desc.present?
84
+ insertion << " (default: #{default})" unless default.nil?
85
+ insertion.prepend "**REQUIRED** " if required
86
+
87
+ copy_file "DEPLOYMENT.md" unless File.exist?("DEPLOYMENT.md")
88
+ inject_into_file "DEPLOYMENT.md", "#{insertion}\n- ", after: /^## Environment variables.*?^- /m
89
+ end
90
+
91
+ def add_lint_task(task, fix: "#{task}:autocorrect")
92
+ unless File.read("README.md").include?(" and lint checks")
93
+ inject_into_file "README.md", " and lint checks", after: "automated tests"
94
+ inject_into_file "README.md", <<~MARKDOWN, after: "```\nbin/rake\n```\n"
95
+
96
+ > [!NOTE]
97
+ > Rake allows you to run all checks in parallel with the `-m` option. This is much faster, but since the output is interleaved, it may be harder to read.
98
+
99
+ ```
100
+ bin/rake -m
101
+ ```
102
+
103
+ ### Fixing lint issues
104
+
105
+ Some lint issues can be auto-corrected. To fix them, run:
106
+
107
+ ```
108
+ bin/rake fix
109
+ ```
110
+ MARKDOWN
111
+ end
112
+
113
+ unless File.read("Rakefile").include?("task fix:")
114
+ append_to_file "Rakefile", <<~RUBY
115
+
116
+ desc "Apply auto-corrections"
117
+ task fix: %w[] do
118
+ Thor::Base.shell.new.say_status :OK, "All fixes applied!"
119
+ end
120
+ RUBY
121
+ end
122
+
123
+ inject_into_file "Rakefile", " #{task}", after: /task default: %w\[[^\]]*/
124
+ inject_into_file "Rakefile", " #{fix}", after: /task fix: %w\[[^\]]*/
125
+ end
126
+
127
+ def gitignore(*lines)
128
+ return unless File.exist?(".gitignore")
129
+
130
+ lines -= File.read(".gitignore").lines(chomp: true)
131
+ return if lines.empty?
132
+
133
+ text = lines.map(&:strip).join("\n")
134
+ append_to_file ".gitignore", "#{text}\n"
135
+ end
136
+
137
+ def prevent_autoload_lib(*paths)
138
+ return unless File.read("config/application.rb").match?(/^\s*config.autoload_lib/)
139
+
140
+ paths.reverse_each do |path|
141
+ gsub_file("config/application.rb", /autoload_lib\(ignore: %w.*$/) do |match|
142
+ next match if match.match?(/%w.*[\(\[ ]#{Regexp.quote(path)}[ \)\]]/)
143
+
144
+ match.sub(/%w[\(\[]/, '\0' + path + " ")
145
+ end
146
+ end
147
+ end
148
+
149
+ def die(message = nil)
150
+ message = message.sub(/^/, "ERROR: ") if message && !message.start_with?(/error/i)
151
+
152
+ raise Thor::Error, message
153
+ end
154
+ end
155
+ end
@@ -0,0 +1,19 @@
1
+ require "thor"
2
+
3
+ module Nextgen
4
+ class CLI < Thor
5
+ extend ThorExtensions
6
+
7
+ map %w[-v --version] => "version"
8
+
9
+ desc "create APP_PATH", "Generate a Rails app interactively in APP_PATH"
10
+ def create(app_path)
11
+ Commands::Create.run(app_path, options)
12
+ end
13
+
14
+ desc "version", "Display nextgen version", hide: true
15
+ def version
16
+ say "nextgen/#{VERSION} #{RUBY_DESCRIPTION}"
17
+ end
18
+ end
19
+ end