From 7bccc2c635f35d3ada302627a36423ac9dd0d5ec Mon Sep 17 00:00:00 2001 From: Matt Brictson Date: Sun, 29 Sep 2024 12:30:11 -0700 Subject: [PATCH] Support generating apps with npm instead of yarn (#122) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- README.md | 9 +- config/generators.yml | 6 +- lib/nextgen/actions.rb | 2 +- lib/nextgen/actions/javascript.rb | 50 ++++++++++ lib/nextgen/actions/yarn.rb | 40 -------- lib/nextgen/commands/create.rb | 29 ++++-- lib/nextgen/generators/eslint.rb | 12 +-- lib/nextgen/generators/node.rb | 1 - lib/nextgen/generators/npm.rb | 16 ++++ lib/nextgen/generators/stylelint.rb | 12 +-- lib/nextgen/generators/vite.rb | 23 +++-- lib/nextgen/rails_options.rb | 15 ++- template/.overcommit.yml.tt | 2 +- template/bin/setup | 14 ++- .../lib/tasks/{eslint.rake => eslint.rake.tt} | 4 +- .../{stylelint.rake => stylelint.rake.tt} | 4 +- test/e2e/nextgen_e2e_test.rb | 4 +- test/integration/generators/npm_test.rb | 96 +++++++++++++++++++ test/nextgen/rails_options_test.rb | 11 +++ 19 files changed, 265 insertions(+), 85 deletions(-) create mode 100644 lib/nextgen/actions/javascript.rb delete mode 100644 lib/nextgen/actions/yarn.rb create mode 100644 lib/nextgen/generators/npm.rb rename template/lib/tasks/{eslint.rake => eslint.rake.tt} (63%) rename template/lib/tasks/{stylelint.rake => stylelint.rake.tt} (65%) create mode 100644 test/integration/generators/npm_test.rb diff --git a/README.md b/README.md index a5ebc50..17fb326 100644 --- a/README.md +++ b/README.md @@ -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). @@ -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. diff --git a/config/generators.yml b/config/generators.yml index 7e4d92c..fb458a1 100644 --- a/config/generators.yml +++ b/config/generators.yml @@ -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" diff --git a/lib/nextgen/actions.rb b/lib/nextgen/actions.rb index 474673b..83d1752 100644 --- a/lib/nextgen/actions.rb +++ b/lib/nextgen/actions.rb @@ -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 diff --git a/lib/nextgen/actions/javascript.rb b/lib/nextgen/actions/javascript.rb new file mode 100644 index 0000000..af43001 --- /dev/null +++ b/lib/nextgen/actions/javascript.rb @@ -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 diff --git a/lib/nextgen/actions/yarn.rb b/lib/nextgen/actions/yarn.rb deleted file mode 100644 index d6aaf22..0000000 --- a/lib/nextgen/actions/yarn.rb +++ /dev/null @@ -1,40 +0,0 @@ -# frozen_string_literal: true - -module Nextgen - module Actions::Yarn - def add_yarn_packages(*packages, dev: false) - add = dev ? "add -D" : "add" - yarn_command "#{add} #{packages.map(&:shellescape).join(" ")}" - end - alias add_yarn_package add_yarn_packages - - def remove_yarn_packages(*packages, capture: false) - yarn_command "remove #{packages.map(&:shellescape).join(" ")}", capture: - end - alias remove_yarn_package remove_yarn_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 yarn_command(cmd, capture: false) - say_status :yarn, cmd, :green - output = run! "yarn #{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 diff --git a/lib/nextgen/commands/create.rb b/lib/nextgen/commands/create.rb index d416729..c5df6d8 100644 --- a/lib/nextgen/commands/create.rb +++ b/lib/nextgen/commands/create.rb @@ -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 @@ -67,10 +70,11 @@ 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 @@ -78,7 +82,7 @@ def run # rubocop:disable Metrics/MethodLength, Metrics/PerceivedComplexity 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}" @@ -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 @@ -240,9 +245,13 @@ 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 @@ -250,6 +259,14 @@ def ask_optional_enhancements 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)) diff --git a/lib/nextgen/generators/eslint.rb b/lib/nextgen/generators/eslint.rb index 9a60795..b389a06 100644 --- a/lib/nextgen/generators/eslint.rb +++ b/lib/nextgen/generators/eslint.rb @@ -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", @@ -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") @@ -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 diff --git a/lib/nextgen/generators/node.rb b/lib/nextgen/generators/node.rb index 1b4d133..dab8556 100644 --- a/lib/nextgen/generators/node.rb +++ b/lib/nextgen/generators/node.rb @@ -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.*$/ diff --git a/lib/nextgen/generators/npm.rb b/lib/nextgen/generators/npm.rb new file mode 100644 index 0000000..c311bc8 --- /dev/null +++ b/lib/nextgen/generators/npm.rb @@ -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 diff --git a/lib/nextgen/generators/stylelint.rb b/lib/nextgen/generators/stylelint.rb index 98169a1..c764293 100644 --- a/lib/nextgen/generators/stylelint.rb +++ b/lib/nextgen/generators/stylelint.rb @@ -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", @@ -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") @@ -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 diff --git a/lib/nextgen/generators/vite.rb b/lib/nextgen/generators/vite.rb index cae0034..f59f319 100644 --- a/lib/nextgen/generators/vite.rb +++ b/lib/nextgen/generators/vite.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true say_git "Remove esbuild" -remove_yarn_package "esbuild" +remove_js_package "esbuild" remove_package_json_script(:build) say_git "Install the vite_rails gem" @@ -22,15 +22,14 @@ RUBY say_git "Run the vite installer" -FileUtils.touch "yarn.lock" bundle_command "exec vite install" gsub_file "app/views/layouts/application.html.erb", /vite_javascript_tag 'application' %>/, 'vite_javascript_tag "application", "data-turbo-track": "reload" %>' say_git "Replace vite-plugin-ruby with vite-plugin-rails" -add_yarn_packages "rollup@^4.2.0", "vite-plugin-rails" -remove_yarn_package "vite-plugin-ruby" +add_js_packages "rollup@^4.2.0", "vite-plugin-rails" +remove_js_package "vite-plugin-ruby" gsub_file "vite.config.ts", "import RubyPlugin from 'vite-plugin-ruby'", 'import ViteRails from "vite-plugin-rails"' gsub_file "vite.config.ts", /^\s*?RubyPlugin\(\)/, <<~TYPESCRIPT.gsub(/^/, " ").rstrip ViteRails({ @@ -44,12 +43,12 @@ say_git "Move vite package from devDependencies to dependencies" if (vite_version = File.read("package.json")[/"vite":\s*"(.+?)"/, 1]) - remove_yarn_package "vite", capture: true - add_yarn_package "vite@#{vite_version}" + remove_js_package "vite", capture: true + add_js_packages "vite@#{vite_version}" end say_git "Install autoprefixer" -add_yarn_packages "postcss@^8.4.24", "autoprefixer@^10.4.14" +add_js_packages "postcss@^8.4.24", "autoprefixer@^10.4.14" copy_file "postcss.config.cjs" # TODO: rspec support @@ -61,7 +60,7 @@ end say_git "Install modern-normalize and base stylesheets" -add_yarn_package "modern-normalize@^3.0.0" +add_js_packages "modern-normalize@^3.0.0" copy_file "app/frontend/stylesheets/index.css" copy_file "app/frontend/stylesheets/base.css" copy_file "app/frontend/stylesheets/reset.css" @@ -74,7 +73,7 @@ JS end if package_json.match?(%r{@hotwired/stimulus}) - add_yarn_package "stimulus-vite-helpers" + add_js_package "stimulus-vite-helpers" copy_file "app/frontend/controllers/index.js", force: true prepend_to_file "app/frontend/entrypoints/application.js", <<~JS import "~/controllers"; @@ -97,7 +96,7 @@ copy_file "test/helpers/inline_svg_helper_test.rb" if File.exist?("test/vite_helper.rb") say_git "Add a `bin/dev` script that uses run-pty" -add_yarn_package "run-pty@^5", dev: true +add_js_packages "run-pty@^5", dev: true copy_file "bin/dev-node", "bin/dev", mode: :preserve, force: true copy_file "run-pty.json" remove_file "Procfile.dev" @@ -129,9 +128,9 @@ 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 YAML end diff --git a/lib/nextgen/rails_options.rb b/lib/nextgen/rails_options.rb index 5b0d5aa..22efbc6 100644 --- a/lib/nextgen/rails_options.rb +++ b/lib/nextgen/rails_options.rb @@ -19,7 +19,9 @@ class RailsOptions jbuilder ].freeze - attr_reader :asset_pipeline, :css, :javascript, :database, :test_framework + JS_PACKAGE_MANAGERS = %i[npm yarn].freeze + + attr_reader :asset_pipeline, :css, :javascript, :js_package_manager, :database, :test_framework def_delegators :version, :asset_pipelines, :databases, :default_features, :optional_features @@ -31,6 +33,7 @@ def initialize(version:) @skip_features = [] @skip_system_test = false @test_framework = :minitest + @js_package_manager = :yarn end def version_label @@ -57,6 +60,16 @@ def javascript=(framework) @javascript = framework end + def js_package_manager=(tool) + raise ArgumentError, "Unknown package manager: #{tool}" unless JS_PACKAGE_MANAGERS.include?(tool) + + @js_package_manager = tool + end + + def npm? + js_package_manager == :npm + end + def vite! self.asset_pipeline = nil @javascript = "esbuild" diff --git a/template/.overcommit.yml.tt b/template/.overcommit.yml.tt index 27c3dde..c0d1b3d 100644 --- a/template/.overcommit.yml.tt +++ b/template/.overcommit.yml.tt @@ -82,7 +82,7 @@ PreCommit: exclude: - "**/db/structure.sql" -<% if File.exist?("yarn.lock") -%> +<% if yarn? -%> YarnCheck: enabled: true <% end -%> diff --git a/template/bin/setup b/template/bin/setup index 6f3a9a9..93eb15d 100755 --- a/template/bin/setup +++ b/template/bin/setup @@ -9,7 +9,7 @@ def setup! run "bundle install" if bundle_needed? run "overcommit --install" if overcommit_installable? run "bin/rails db:prepare" if database_present? - run "yarn install --check-files" if yarn_needed? + run install_node_packages_command if node_present? run "bin/rails tmp:create" if tmp_missing? run "bin/rails restart" if pid_present? @@ -48,8 +48,16 @@ def database_present? File.exist?("config/database.yml") end -def yarn_needed? - File.exist?("yarn.lock") +def node_present? + File.exist?("package.json") +end + +def install_node_packages_command + if File.exist?("yarn.lock") + "yarn install --check-files" + else + "npm install" + end end def tmp_missing? diff --git a/template/lib/tasks/eslint.rake b/template/lib/tasks/eslint.rake.tt similarity index 63% rename from template/lib/tasks/eslint.rake rename to template/lib/tasks/eslint.rake.tt index 5f17470..794e223 100644 --- a/template/lib/tasks/eslint.rake +++ b/template/lib/tasks/eslint.rake.tt @@ -2,12 +2,12 @@ desc "Run ESLint" task :eslint do - sh "yarn lint:js" + sh "<%= js_package_manager %> run lint:js" end namespace :eslint do desc "Autocorrect ESLint offenses" task :autocorrect do - sh "yarn fix:js" + sh "<%= js_package_manager %> run fix:js" end end diff --git a/template/lib/tasks/stylelint.rake b/template/lib/tasks/stylelint.rake.tt similarity index 65% rename from template/lib/tasks/stylelint.rake rename to template/lib/tasks/stylelint.rake.tt index 2ff4666..ac11572 100644 --- a/template/lib/tasks/stylelint.rake +++ b/template/lib/tasks/stylelint.rake.tt @@ -2,12 +2,12 @@ desc "Run Stylelint" task :stylelint do - sh "yarn lint:css" + sh "<%= js_package_manager %> run lint:css" end namespace :stylelint do desc "Autocorrect Stylelint offenses" task :autocorrect do - sh "yarn fix:css" + sh "<%= js_package_manager %> run fix:css" end end diff --git a/test/e2e/nextgen_e2e_test.rb b/test/e2e/nextgen_e2e_test.rb index 16be530..60a7829 100644 --- a/test/e2e/nextgen_e2e_test.rb +++ b/test/e2e/nextgen_e2e_test.rb @@ -25,9 +25,9 @@ class NextgenE2ETest < Minitest::Test def test_nextgen_generates_rails_app version_keys = VERSION_KEYSTROKES.fetch((ENV["NEXTGEN_VERSION"] || "current").to_sym) frontend_keys = FRONTEND_KEYSTROKES.fetch((ENV["NEXTGEN_FRONTEND"] || "default").to_sym) - test_framework_keys = TEST_FRAMEWORK_KEYSTROKES.fetch((ENV["NEXTGEN_TEST_FRAMEWORK"] || "minitest").to_sym) + test_keys = TEST_FRAMEWORK_KEYSTROKES.fetch((ENV["NEXTGEN_TEST_FRAMEWORK"] || "minitest").to_sym) - stdin_data = "\n" + version_keys + "\n\n\n" + frontend_keys + "\n\n\n\n\n" + test_framework_keys + "\n\n\u0001\n\n" + stdin_data = "\n" + version_keys + "\n\n\n" + frontend_keys + "\n\n\n\n\n" + test_keys + "\n\n\u0001\n\n\n" assert_bundle_exec_nextgen_create(stdin_data:) end diff --git a/test/integration/generators/npm_test.rb b/test/integration/generators/npm_test.rb new file mode 100644 index 0000000..5bc9979 --- /dev/null +++ b/test/integration/generators/npm_test.rb @@ -0,0 +1,96 @@ +# frozen_string_literal: true + +require_relative "test_case" + +class Nextgen::Generators::NpmTest < Nextgen::Generators::TestCase + destination File.join(Dir.tmpdir, "test_#{SecureRandom.hex(8)}") + setup :prepare_destination + + test "removes yarn.lock and generates a package-json.lock" do + Dir.chdir(destination_root) do + File.write("package.json", "{}\n") + FileUtils.touch("yarn.lock") + FileUtils.touch("README.md") + end + + apply_generator + + assert_no_file "yarn.lock" + assert_file "package-lock.json" + end + + test "removes mentions of yarn from README.md" do + Dir.chdir(destination_root) do + File.write("package.json", "{}\n") + File.write("README.md", <<~MD) + - Node 18 (LTS) or newer + - Yarn 1.x (classic) + + brew install node + brew install yarn + MD + end + + apply_generator + + assert_file "README.md", <<~EXPECTED + - Node 18 (LTS) or newer + + brew install node + EXPECTED + end + + test "removes mentions of yarn from Procfile.dev" do + Dir.chdir(destination_root) do + File.write("package.json", "{}\n") + FileUtils.touch("README.md") + File.write("Procfile.dev", <<~PROCFILE) + web: env RUBY_DEBUG_OPEN=true bin/rails server + js: yarn build --watch + PROCFILE + end + + apply_generator + + assert_file "Procfile.dev", <<~EXPECTED + web: env RUBY_DEBUG_OPEN=true bin/rails server + js: npm run build -- --watch + EXPECTED + end + + test "removes mentions of yarn from Dockerfile" do + Dir.chdir(destination_root) do + File.write("package.json", "{}\n") + FileUtils.touch("README.md") + File.write("Dockerfile", <<~'DOCKER') + # Install JavaScript dependencies + ARG NODE_VERSION=20.17.0 + ARG YARN_VERSION=1.22.22 + ENV PATH=/usr/local/node/bin:$PATH + RUN curl -sL https://github.com/nodenv/node-build/archive/master.tar.gz | tar xz -C /tmp/ && \ + /tmp/node-build-master/bin/node-build "${NODE_VERSION}" /usr/local/node && \ + npm install -g yarn@$YARN_VERSION && \ + rm -rf /tmp/node-build-master + + # Install node modules + COPY package.json yarn.lock ./ + RUN yarn install --frozen-lockfile + DOCKER + end + + apply_generator + + assert_file "Dockerfile", <<~'EXPECTED' + # Install JavaScript dependencies + ARG NODE_VERSION=20.17.0 + ENV PATH=/usr/local/node/bin:$PATH + RUN curl -sL https://github.com/nodenv/node-build/archive/master.tar.gz | tar xz -C /tmp/ && \ + /tmp/node-build-master/bin/node-build "${NODE_VERSION}" /usr/local/node && \ + rm -rf /tmp/node-build-master + + # Install node modules + COPY package.json package-lock.json ./ + RUN npm ci + EXPECTED + end +end diff --git a/test/nextgen/rails_options_test.rb b/test/nextgen/rails_options_test.rb index a60419e..34b1dca 100644 --- a/test/nextgen/rails_options_test.rb +++ b/test/nextgen/rails_options_test.rb @@ -83,6 +83,16 @@ def test_javascript_can_be_skipped_by_assigning_nil assert_equal(["--skip-javascript"], opts.to_args) end + def test_js_package_manager_can_be_specified + opts = build_rails_options + + opts.js_package_manager = :npm + assert(opts.npm?) + + opts.js_package_manager = :yarn + refute(opts.npm?) + end + def test_asset_pipeline_can_be_specified opts = build_rails_options @@ -226,6 +236,7 @@ def test_defaults refute(opts.skip_asset_pipeline?) refute(opts.skip_javascript?) refute(opts.skip_system_test?) + refute(opts.npm?) assert_empty(opts.to_args) end