Skip to content

Commit 230d190

Browse files
Add dynamic package manager detection for error messages
Detect package manager (yarn/pnpm/bun/npm) and show correct commands in error messages instead of hardcoding yarn commands. Detection strategy: 1. Check packageManager field in package.json (Node.js Corepack standard) 2. Fall back to lock file detection (yarn.lock, pnpm-lock.yaml, etc.) 3. Default to yarn for backward compatibility Changes: - Add Utils.detect_package_manager method - Add Utils.package_manager_install_exact_command method - Add Utils.package_manager_remove_command method - Update all 6 error messages in VersionChecker to use dynamic commands - Add 22 comprehensive specs with 100% coverage Supported package managers: - yarn: yarn add package@1.0.0 --exact - pnpm: pnpm add package@1.0.0 --save-exact - bun: bun add package@1.0.0 --exact - npm: npm install package@1.0.0 --save-exact 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
1 parent 4f18aa5 commit 230d190

File tree

3 files changed

+340
-9
lines changed

3 files changed

+340
-9
lines changed

lib/react_on_rails/utils.rb

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -283,6 +283,81 @@ def self.prepend_to_file_if_text_not_present(file:, text_to_prepend:, regex:)
283283
puts "Prepended\n#{text_to_prepend}to #{file}."
284284
end
285285

286+
# Detects which package manager is being used.
287+
# First checks the packageManager field in package.json (Node.js Corepack standard),
288+
# then falls back to checking for lock files.
289+
#
290+
# @return [Symbol] The package manager symbol (:npm, :yarn, :pnpm, :bun)
291+
def self.detect_package_manager
292+
manager = detect_package_manager_from_package_json || detect_package_manager_from_lock_files
293+
manager || :yarn # Default to yarn if no detection succeeds
294+
end
295+
296+
private_class_method def self.detect_package_manager_from_package_json
297+
package_json_path = File.join(Rails.root, ReactOnRails.configuration.node_modules_location, "package.json")
298+
return nil unless File.exist?(package_json_path)
299+
300+
package_json_data = JSON.parse(File.read(package_json_path))
301+
return nil unless package_json_data["packageManager"]
302+
303+
manager_string = package_json_data["packageManager"]
304+
# Extract manager name from strings like "yarn@3.6.0" or "pnpm@8.0.0"
305+
manager_name = manager_string.split("@").first
306+
manager_name.to_sym if %w[npm yarn pnpm bun].include?(manager_name)
307+
rescue StandardError
308+
nil
309+
end
310+
311+
private_class_method def self.detect_package_manager_from_lock_files
312+
root = Rails.root
313+
return :yarn if File.exist?(File.join(root, "yarn.lock"))
314+
return :pnpm if File.exist?(File.join(root, "pnpm-lock.yaml"))
315+
return :bun if File.exist?(File.join(root, "bun.lockb"))
316+
return :npm if File.exist?(File.join(root, "package-lock.json"))
317+
318+
nil
319+
end
320+
321+
# Returns the appropriate install command for the detected package manager.
322+
# Generates the correct command with exact version syntax.
323+
#
324+
# @param package_name [String] The name of the package to install
325+
# @param version [String] The exact version to install
326+
# @return [String] The command to run (e.g., "yarn add react-on-rails@16.0.0 --exact")
327+
def self.package_manager_install_exact_command(package_name, version)
328+
manager = detect_package_manager
329+
330+
case manager
331+
when :pnpm
332+
"pnpm add #{package_name}@#{version} --save-exact"
333+
when :bun
334+
"bun add #{package_name}@#{version} --exact"
335+
when :npm
336+
"npm install #{package_name}@#{version} --save-exact"
337+
else # :yarn or unknown, default to yarn
338+
"yarn add #{package_name}@#{version} --exact"
339+
end
340+
end
341+
342+
# Returns the appropriate remove command for the detected package manager.
343+
#
344+
# @param package_name [String] The name of the package to remove
345+
# @return [String] The command to run (e.g., "yarn remove react-on-rails")
346+
def self.package_manager_remove_command(package_name)
347+
manager = detect_package_manager
348+
349+
case manager
350+
when :pnpm
351+
"pnpm remove #{package_name}"
352+
when :bun
353+
"bun remove #{package_name}"
354+
when :npm
355+
"npm uninstall #{package_name}"
356+
else # :yarn or unknown, default to yarn
357+
"yarn remove #{package_name}"
358+
end
359+
end
360+
286361
def self.default_troubleshooting_section
287362
<<~DEFAULT
288363
📞 Get Help & Support:

lib/react_on_rails/version_checker.rb

Lines changed: 24 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,9 @@ def validate_version_and_package_compatibility!
3737
def validate_package_json_exists!
3838
return if File.exist?(node_package_version.package_json)
3939

40+
base_install_cmd = ReactOnRails::Utils.package_manager_install_exact_command("react-on-rails", gem_version)
41+
pro_install_cmd = ReactOnRails::Utils.package_manager_install_exact_command("react-on-rails-pro", gem_version)
42+
4043
raise ReactOnRails::Error, <<~MSG.strip
4144
**ERROR** ReactOnRails: package.json file not found.
4245
@@ -47,10 +50,10 @@ def validate_package_json_exists!
4750
4851
Fix:
4952
1. Ensure you have a package.json in your project root
50-
2. Run: yarn add react-on-rails@#{gem_version} --exact
53+
2. Run: #{base_install_cmd}
5154
5255
Or if using React on Rails Pro:
53-
Run: yarn add react-on-rails-pro@#{gem_version} --exact
56+
Run: #{pro_install_cmd}
5457
MSG
5558
end
5659

@@ -61,6 +64,8 @@ def validate_package_gem_compatibility!
6164

6265
# Error: Both packages installed
6366
if has_base_package && has_pro_package
67+
remove_cmd = ReactOnRails::Utils.package_manager_remove_command("react-on-rails")
68+
6469
raise ReactOnRails::Error, <<~MSG.strip
6570
**ERROR** ReactOnRails: Both 'react-on-rails' and 'react-on-rails-pro' packages are installed.
6671
@@ -69,7 +74,7 @@ def validate_package_gem_compatibility!
6974
7075
Fix:
7176
1. Remove 'react-on-rails' from your package.json dependencies
72-
2. Run: yarn remove react-on-rails
77+
2. Run: #{remove_cmd}
7378
3. Keep only: react-on-rails-pro
7479
7580
#{package_json_location}
@@ -78,14 +83,17 @@ def validate_package_gem_compatibility!
7883

7984
# Error: Pro gem but using base package
8085
if is_pro_gem && !has_pro_package
86+
remove_cmd = ReactOnRails::Utils.package_manager_remove_command("react-on-rails")
87+
install_cmd = ReactOnRails::Utils.package_manager_install_exact_command("react-on-rails-pro", gem_version)
88+
8189
raise ReactOnRails::Error, <<~MSG.strip
8290
**ERROR** ReactOnRails: You have the Pro gem installed but are using the base 'react-on-rails' package.
8391
8492
When using React on Rails Pro, you must use the 'react-on-rails-pro' npm package.
8593
8694
Fix:
87-
1. Remove the base package: yarn remove react-on-rails
88-
2. Install the Pro package: yarn add react-on-rails-pro@#{gem_version} --exact
95+
1. Remove the base package: #{remove_cmd}
96+
2. Install the Pro package: #{install_cmd}
8997
9098
#{package_json_location}
9199
MSG
@@ -94,6 +102,9 @@ def validate_package_gem_compatibility!
94102
# Error: Pro package but not Pro gem
95103
return unless !is_pro_gem && has_pro_package
96104

105+
remove_pro_cmd = ReactOnRails::Utils.package_manager_remove_command("react-on-rails-pro")
106+
install_base_cmd = ReactOnRails::Utils.package_manager_install_exact_command("react-on-rails", gem_version)
107+
97108
raise ReactOnRails::Error, <<~MSG.strip
98109
**ERROR** ReactOnRails: You have the 'react-on-rails-pro' package installed but the Pro gem is not installed.
99110
@@ -105,8 +116,8 @@ def validate_package_gem_compatibility!
105116
2. Run: bundle install
106117
107118
Or if you meant to use the base version:
108-
1. Remove the Pro package: yarn remove react-on-rails-pro
109-
2. Install the base package: yarn add react-on-rails@#{gem_version} --exact
119+
1. Remove the Pro package: #{remove_pro_cmd}
120+
2. Install the base package: #{install_base_cmd}
110121
111122
#{package_json_location}
112123
MSG
@@ -118,6 +129,8 @@ def validate_exact_version!
118129
return unless node_package_version.semver_wildcard?
119130

120131
package_name = node_package_version.package_name
132+
install_cmd = ReactOnRails::Utils.package_manager_install_exact_command(package_name, gem_version)
133+
121134
raise ReactOnRails::Error, <<~MSG.strip
122135
**ERROR** ReactOnRails: The '#{package_name}' package version is not an exact version.
123136
@@ -128,7 +141,7 @@ def validate_exact_version!
128141
Do not use ^, ~, >, <, *, or other semver ranges.
129142
130143
Fix:
131-
Run: yarn add #{package_name}@#{gem_version} --exact
144+
Run: #{install_cmd}
132145
133146
#{package_json_location}
134147
MSG
@@ -140,6 +153,8 @@ def validate_version_match!
140153
return if node_package_version.parts == gem_version_parts
141154

142155
package_name = node_package_version.package_name
156+
install_cmd = ReactOnRails::Utils.package_manager_install_exact_command(package_name, gem_version)
157+
143158
raise ReactOnRails::Error, <<~MSG.strip
144159
**ERROR** ReactOnRails: The '#{package_name}' package version does not match the gem version.
145160
@@ -149,7 +164,7 @@ def validate_version_match!
149164
The npm package and gem versions must match exactly for compatibility.
150165
151166
Fix:
152-
Run: yarn add #{package_name}@#{gem_version} --exact
167+
Run: #{install_cmd}
153168
154169
#{package_json_location}
155170
MSG

0 commit comments

Comments
 (0)