Skip to content

Commit

Permalink
Support generating apps with npm instead of yarn (#122)
Browse files Browse the repository at this point in the history
If you select options that require a JavaScript runtime (postcss, Vite,
ESLint, etc.), nextgen will now ask what package manager you'd like to
use:

```
Which JavaScript package manager will you use? 
‣ yarn (default)
  npm
```

If you choose npm, nextgen will scrub yarn references from the files
that Rails provides (Dockerfile, Procfile.dev) and replace them with
appropriate npm commands. The `bin/setup` script provided by nextgen now
autodetects yarn vs npm and runs the correct install command as well.

Yarn remains the default option.

Closes #118.
  • Loading branch information
mattbrictson authored Sep 29, 2024
1 parent 6d7f80a commit 7bccc2c
Show file tree
Hide file tree
Showing 19 changed files with 265 additions and 85 deletions.
9 changes: 8 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ Nextgen generates apps using **Rails 7.2**.

- **Ruby 3.1+** is required (Ruby 3.2 if you choose a Rails 8 pre-release)
- **Rubygems 3.4.8+** is required (run `gem update --system` to get it)
- **Node 18.12+ and Yarn** are required if you choose Vite or other Node-based options
- **Node 18.12+ and Yarn** are required if you choose Vite or other Node-based options (see the [npm note](#yarn-or-npm) below)
- Additional tools may be required depending on the options you select (e.g. PostgreSQL)

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).
Expand Down Expand Up @@ -71,6 +71,13 @@ If you've opted into GitHub Actions, Nextgen will automatically add jobs to your

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.

### Yarn or npm

Prefer npm? Nextgen allows you to choose Yarn or npm to manage your app's JavaScript dependencies. Your Dockerfile, CI jobs, `bin/setup` script, etc. will be adjusted appropriately.

> [!NOTE]
> As of Rails 8.0, `rails new` is still hard-coded to use Yarn in some places. Therefore you may still need Yarn installed on your system in order to generate a new app. Nextgen will remove these Yarn references from your generated project if you select the npm option.
### Opinionated RuboCop Config

By default, Rails apps include RuboCop with a config defined by the [rubocop-rails-omakase](https://github.com/rails/rubocop-rails-omakase) gem. Nextgen allows you to opt out of RuboCop entirely, or use Nextgen's own custom RuboCop config. Nextgen's config will automatically include Capybara, FactoryBot, and RSpec rules, should your app include those frameworks.
Expand Down
6 changes: 5 additions & 1 deletion config/generators.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,11 @@ rspec_github_actions:
requires: rspec

node:
description: "Set up Node and Yarn"
description: "Set up Node"

npm:
description: "Use npm as JavaScript package manager"
requires: npm

vite:
description: "Replace the asset pipeline with Vite in app/frontend"
Expand Down
2 changes: 1 addition & 1 deletion lib/nextgen/actions.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ module Nextgen
module Actions
include Bundler
include Git
include Yarn
include Javascript

def with_nextgen_source_path
path = Nextgen.template_path.to_s
Expand Down
50 changes: 50 additions & 0 deletions lib/nextgen/actions/javascript.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# frozen_string_literal: true

module Nextgen
module Actions::Javascript
def add_js_packages(*packages, dev: false)
command = yarn? ? +"yarn add" : +"npm install --fund=false --audit-false"
command << " -D" if dev
run_js_command "#{command} #{packages.map(&:shellescape).join(" ")}"
end
alias add_js_package add_js_packages

def remove_js_packages(*packages, capture: false)
command = yarn? ? "yarn remove" : "npm uninstall"
run_js_command "#{command} #{packages.map(&:shellescape).join(" ")}", capture:
end
alias remove_js_package remove_js_packages

def add_package_json_scripts(scripts)
scripts.each do |name, script|
cmd = "npm pkg set scripts.#{name.to_s.shellescape}=#{script.shellescape}"
say_status :run, cmd.truncate(60), :green
run! cmd, verbose: false
end
end
alias add_package_json_script add_package_json_scripts

def remove_package_json_script(name)
cmd = "npm pkg delete scripts.#{name.to_s.shellescape}"
say_status :run, cmd.truncate(60), :green
run! cmd, verbose: false
end

def js_package_manager
File.exist?("yarn.lock") ? :yarn : :npm
end

def yarn?
js_package_manager == :yarn
end

def run_js_command(cmd, capture: false)
say_status(*cmd.split(" ", 2), :green)
output = run! cmd, capture: true, verbose: false
return output if capture
return puts(output) unless output.match?(/^success /)

puts output.lines.grep(/^(warning|success) /).join
end
end
end
40 changes: 0 additions & 40 deletions lib/nextgen/actions/yarn.rb

This file was deleted.

29 changes: 23 additions & 6 deletions lib/nextgen/commands/create.rb
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,10 @@ def run # rubocop:disable Metrics/MethodLength, Metrics/PerceivedComplexity
ask_rails_frameworks
ask_test_framework
ask_system_testing if rails_opts.frontend? && rails_opts.test_framework?
reload_generators
ask_optional_enhancements
ask_js_package_manager if node?
reload_generators

say <<~SUMMARY
Expand All @@ -67,18 +70,19 @@ def run # rubocop:disable Metrics/MethodLength, Metrics/PerceivedComplexity

if node?
say <<~NODE
Based on the options you selected, your app will require Node and Yarn. For reference, you are using these versions:
Based on the options you selected, your app will require a JavaScript runtime. For reference, you are using:
Node: #{capture_version("node")}
Yarn: #{capture_version("yarn")}
node: #{capture_version("node")}
yarn: #{capture_version("yarn")}
npm: #{capture_version("npm")}
NODE
end

continue_if "Continue?"

create_initial_commit_message
copy_package_json if node?
create_package_json if node?
Nextgen::RailsCommand.run "new", *rails_new_args
Dir.chdir(app_path) do
Nextgen::RailsCommand.run "app:template", "LOCATION=#{write_generators_script}"
Expand Down Expand Up @@ -111,12 +115,13 @@ def continue_if(question)
end
end

def copy_package_json
def create_package_json
FileUtils.mkdir_p(app_path)
FileUtils.cp(
Nextgen.template_path.join("package.json"),
File.join(app_path, "package.json")
)
FileUtils.touch(File.join(app_path, rails_opts.npm? ? "package-lock.json" : "yarn.lock"))
end

def ask_rails_version
Expand Down Expand Up @@ -240,16 +245,28 @@ def ask_system_testing
rails_opts.skip_system_test! unless system_testing
end

def ask_optional_enhancements
def reload_generators
selected = generators ? (generators.all_active & generators.optional.values) : []
@generators = Generators.compatible_with(rails_opts:)
generators.activate(*(selected & generators.optional.values))
end

def ask_optional_enhancements
answers = prompt.multi_select(
"Which optional enhancements would you like to add?",
generators.optional.sort_by { |label, _| label.downcase }.to_h
)
generators.activate(*answers)
end

def ask_js_package_manager
options = {
"yarn (default)" => :yarn,
"npm" => :npm
}
rails_opts.js_package_manager = prompt_select("Which JavaScript package manager will you use?", options)
end

def create_initial_commit_message
path = File.join(app_path, "tmp", "initial_nextgen_commit")
FileUtils.mkdir_p(File.dirname(path))
Expand Down
12 changes: 6 additions & 6 deletions lib/nextgen/generators/eslint.rb
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# frozen_string_literal: true

say_git "Install eslint"
add_yarn_packages(
add_js_packages(
"@eslint/js",
"eslint@^9",
"eslint-config-prettier",
Expand All @@ -21,7 +21,7 @@
copy_file "eslint.config.js"

say_git "Add eslint to default rake task"
copy_file "lib/tasks/eslint.rake"
template "lib/tasks/eslint.rake.tt"
add_lint_task "eslint"

if File.exist?(".github/workflows/ci.yml")
Expand All @@ -39,16 +39,16 @@
uses: actions/setup-node@v4
with:
#{node_spec}
cache: yarn
cache: #{js_package_manager}
- name: Install Yarn packages
- name: Install #{js_package_manager} packages
run: npx --yes ci
- name: Lint JavaScript files with eslint
run: yarn lint:js
run: #{js_package_manager} run lint:js
YAML
end

say_git "Auto-correct any existing issues"
run "yarn fix:js", capture: true
run "#{js_package_manager} run fix:js", capture: true
1 change: 0 additions & 1 deletion lib/nextgen/generators/node.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
# frozen_string_literal: true

say_git "Add Node and Yarn prerequisites"
copy_file "package.json" unless File.exist?("package.json")
inject_into_file "README.md", "\n- Node 18 (LTS) or newer\n- Yarn 1.x (classic)", after: /^- Ruby.*$/
inject_into_file "README.md", "\nbrew install node\nbrew install yarn", after: /^brew install rbenv.*$/
Expand Down
16 changes: 16 additions & 0 deletions lib/nextgen/generators/npm.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# frozen_string_literal: true

remove_file "yarn.lock"
run! "npm install --fund=false --audit=false"

gsub_file "README.md", "\n- Yarn 1.x (classic)", ""
gsub_file "README.md", "\nbrew install yarn", ""

gsub_file "Procfile.dev", "yarn build", "npm run build --" if File.exist?("Procfile.dev")

if File.exist?("Dockerfile")
gsub_file "Dockerfile", /^\s*ARG YARN_VERSION.*\n/, ""
gsub_file "Dockerfile", /^\s*npm install -g yarn.*\n/, ""
gsub_file "Dockerfile", "yarn.lock", "package-lock.json"
gsub_file "Dockerfile", /RUN yarn install.*/, "RUN npm ci"
end
12 changes: 6 additions & 6 deletions lib/nextgen/generators/stylelint.rb
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# frozen_string_literal: true

say_git "Install stylelint"
add_yarn_packages(
add_js_packages(
"stylelint",
"stylelint-config-standard",
"stylelint-declaration-strict-value",
Expand All @@ -19,7 +19,7 @@
copy_file ".stylelintrc.js"

say_git "Add stylelint to default rake task"
copy_file "lib/tasks/stylelint.rake"
template "lib/tasks/stylelint.rake.tt"
add_lint_task "stylelint"

if File.exist?(".github/workflows/ci.yml")
Expand All @@ -37,16 +37,16 @@
uses: actions/setup-node@v4
with:
#{node_spec}
cache: yarn
cache: #{js_package_manager}
- name: Install Yarn packages
- name: Install #{js_package_manager} packages
run: npx --yes ci
- name: Lint CSS files with stylelint
run: yarn lint:css
run: #{js_package_manager} run lint:css
YAML
end

say_git "Auto-correct any existing issues"
run "yarn fix:css", capture: true
run "#{js_package_manager} run fix:css", capture: true
Loading

0 comments on commit 7bccc2c

Please sign in to comment.