Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
213 changes: 167 additions & 46 deletions lib/react_on_rails/version_checker.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,63 +4,153 @@ module ReactOnRails
# Responsible for checking versions of rubygem versus yarn node package
# against each other at runtime.
class VersionChecker
attr_reader :node_package_version
attr_reader :package_json_data

# Semver uses - to separate pre-release, but RubyGems use .
VERSION_PARTS_REGEX = /(\d+)\.(\d+)\.(\d+)(?:[-.]([0-9A-Za-z.-]+))?/

def self.build
new(NodePackageVersion.build)
new(PackageJsonData.build)
end

def initialize(node_package_version)
@node_package_version = node_package_version
def initialize(package_json_data)
@package_json_data = package_json_data
end

# For compatibility, the gem and the node package versions should always match,
# unless the user really knows what they're doing. So we will give a
# warning if they do not.
def log_if_gem_and_node_package_versions_differ
return if node_package_version.raw.nil? || node_package_version.local_path_or_url?
return log_node_semver_version_warning if node_package_version.semver_wildcard?
errors = []

log_differing_versions_warning unless node_package_version.parts == gem_version_parts
# Check if both react-on-rails and react-on-rails-pro are present
check_for_both_packages(errors)

# Check react-on-rails or react-on-rails-pro package
check_main_package_version(errors)

# Check node-renderer package if Pro is present
check_node_renderer_version(errors) if package_json_data.pro_package?

# Handle errors based on environment
handle_errors(errors) if errors.any?
end

private

def common_error_msg
<<-MSG.strip_heredoc
Detected: #{node_package_version.raw}
gem: #{gem_version}
Ensure the installed version of the gem is the same as the version of
your installed Node package. Do not use >= or ~> in your Gemfile for react_on_rails.
Do not use ^, ~, or other non-exact versions in your package.json for react-on-rails.
Run `yarn add react-on-rails --exact` in the directory containing folder node_modules.
def check_for_both_packages(errors)
return unless package_json_data.both_packages?

msg = <<~MSG.strip_heredoc
React on Rails: Both 'react-on-rails' and 'react-on-rails-pro' packages are detected in package.json.
You only need to install 'react-on-rails-pro' package as it already includes 'react-on-rails' as a dependency.
Please remove 'react-on-rails' from your package.json dependencies.
MSG
errors << { type: :warning, message: msg }
end
Comment on lines +44 to +50
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Remove strip_heredoc (redundant with <<~; may be undefined).

Using <<~ already de-indents. Chaining .strip_heredoc can raise NoMethodError depending on ActiveSupport version.

Apply this diff:

-      msg = <<~MSG.strip_heredoc
+      msg = <<~MSG
         React on Rails: Both 'react-on-rails' and 'react-on-rails-pro' packages are detected in package.json.
         You only need to install 'react-on-rails-pro' package as it already includes 'react-on-rails' as a dependency.
         Please remove 'react-on-rails' from your package.json dependencies.
       MSG
-      <<~MSG.strip_heredoc
+      <<~MSG
         React on Rails: Package '#{package_name}' is using a non-exact version: #{raw_version}
         For guaranteed compatibility, you must use exact versions (no ^, ~, >=, etc.).
         Run: yarn add #{package_name}@#{expected_version_for_package(package_name)} --exact
       MSG
-      <<~MSG.strip_heredoc
+      <<~MSG
         React on Rails: Package '#{package_name}' version does not match the gem version.
         Package version: #{package_version}
         Gem version:     #{gem_version}
         Run: yarn add #{package_name}@#{gem_version} --exact
       MSG

Also applies to: 95-101, 103-110

πŸ€– Prompt for AI Agents
In lib/react_on_rails/version_checker.rb around lines 44 to 50, remove the
chained .strip_heredoc call after the <<~ heredoc (<<~ already strips
indentation and .strip_heredoc may be undefined); simply use the <<~MSG heredoc
and assign it to msg. Also apply the same removal for the other occurrences
referenced (around lines 95-101 and 103-110) so none of the heredoc strings call
.strip_heredoc.


def check_main_package_version(errors)
package_name = package_json_data.pro_package? ? "react-on-rails-pro" : "react-on-rails"
package_version_data = package_json_data.get_package_version(package_name)

return if package_version_data.nil?
return if package_version_data.local_path_or_url?

# Check for exact version (no semver wildcards)
if package_version_data.semver_wildcard?
msg = build_semver_wildcard_error(package_name, package_version_data.raw)
errors << { type: :error, message: msg }
return
end

# Check version match
expected_version = package_json_data.pro_package? ? pro_gem_version : gem_version
return if package_version_data.parts == version_parts(expected_version)

msg = build_version_mismatch_error(package_name, package_version_data.raw, expected_version)
errors << { type: :error, message: msg }
end
Comment on lines +66 to +72
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Guard: Pro package present but Pro gem not loaded β†’ clearer error.

If react-on-rails-pro is in package.json but the Pro gem isn’t loaded, pro_gem_version is nil, leading to confusing messages and comparisons.

Apply this diff:

-      # Check version match
-      expected_version = package_json_data.pro_package? ? pro_gem_version : gem_version
+      # Check version match
+      if package_name == "react-on-rails-pro" && pro_gem_version.nil?
+        errors << { type: :error,
+                    message: "React on Rails: 'react-on-rails-pro' is present in package.json, " \
+                             "but the Pro gem is not loaded. Add `gem 'react_on_rails_pro', '#{package_version_data.raw}'` " \
+                             "or remove the package." }
+        return
+      end
+      expected_version = package_json_data.pro_package? ? pro_gem_version : gem_version
πŸ€– Prompt for AI Agents
In lib/react_on_rails/version_checker.rb around lines 66 to 72, the code
compares package.json pro package versions against pro_gem_version which can be
nil when the Pro gem isn't loaded; add a guard that if
package_json_data.pro_package? is true and pro_gem_version is nil then push an
explicit error into errors (e.g., indicate react-on-rails-pro is present in
package.json but the Pro gem is not loaded) and return early, otherwise proceed
to compute expected_version and compare as before so you avoid nil comparisons
and provide a clear diagnostic.


def check_node_renderer_version(errors)
node_renderer_data = package_json_data.get_package_version("@shakacode-tools/react-on-rails-pro-node-renderer")
return if node_renderer_data.nil?
return if node_renderer_data.local_path_or_url?

# Check for exact version
if node_renderer_data.semver_wildcard?
msg = build_semver_wildcard_error("@shakacode-tools/react-on-rails-pro-node-renderer",
node_renderer_data.raw)
errors << { type: :error, message: msg }
return
end

# Check version match with Pro gem
return if node_renderer_data.parts == version_parts(pro_gem_version)

msg = build_version_mismatch_error("@shakacode-tools/react-on-rails-pro-node-renderer",
node_renderer_data.raw, pro_gem_version)
errors << { type: :error, message: msg }
end

def build_semver_wildcard_error(package_name, raw_version)
<<~MSG.strip_heredoc
React on Rails: Package '#{package_name}' is using a non-exact version: #{raw_version}
For guaranteed compatibility, you must use exact versions (no ^, ~, >=, etc.).
Run: yarn add #{package_name}@#{expected_version_for_package(package_name)} --exact
MSG
end

def build_version_mismatch_error(package_name, package_version, gem_version)
<<~MSG.strip_heredoc
React on Rails: Package '#{package_name}' version does not match the gem version.
Package version: #{package_version}
Gem version: #{gem_version}
Run: yarn add #{package_name}@#{gem_version} --exact
MSG
end

def log_differing_versions_warning
msg = "**WARNING** ReactOnRails: ReactOnRails gem and Node package versions do not match\n#{common_error_msg}"
Rails.logger.warn(msg)
def expected_version_for_package(package_name)
case package_name
when "react-on-rails"
gem_version
when "react-on-rails-pro", "@shakacode-tools/react-on-rails-pro-node-renderer"
pro_gem_version
end
end

def log_node_semver_version_warning
msg = "**WARNING** ReactOnRails: Your Node package version for react-on-rails is not an exact version\n" \
"#{common_error_msg}"
Rails.logger.warn(msg)
def handle_errors(errors)
errors.each do |error|
if error[:type] == :warning
Rails.logger.warn("**WARNING** #{error[:message]}")
elsif development_or_test?
raise ReactOnRails::Error, error[:message]
else
Rails.logger.error("**ERROR** #{error[:message]}")
end
end
end

def development_or_test?
Rails.env.development? || Rails.env.test?
end

def gem_version
ReactOnRails::VERSION
end

def gem_version_parts
gem_version.match(VERSION_PARTS_REGEX)&.captures&.compact
def pro_gem_version
return nil unless defined?(ReactOnRailsPro)

ReactOnRailsPro::VERSION
end

class NodePackageVersion
attr_reader :package_json
def version_parts(version)
version&.match(VERSION_PARTS_REGEX)&.captures&.compact
end

# Represents package.json data and provides methods to check for packages
class PackageJsonData
attr_reader :package_json_path

def self.build
new(package_json_path)
Expand All @@ -70,24 +160,61 @@ def self.package_json_path
Rails.root.join(ReactOnRails.configuration.node_modules_location, "package.json")
end

def initialize(package_json)
@package_json = package_json
def initialize(package_json_path)
@package_json_path = package_json_path
end

def pro_package?
package_exists?("react-on-rails-pro")
end

def main_package?
package_exists?("react-on-rails")
end

def both_packages?
main_package? && pro_package?
end

def get_package_version(package_name)
version = find_package_version(package_name)
return nil if version.nil?

PackageVersion.new(version)
end

def raw
return @raw if defined?(@raw)
private

def package_exists?(package_name)
!find_package_version(package_name).nil?
end

if File.exist?(package_json)
parsed_package_contents = JSON.parse(package_json_contents)
if parsed_package_contents.key?("dependencies") &&
parsed_package_contents["dependencies"].key?("react-on-rails")
return @raw = parsed_package_contents["dependencies"]["react-on-rails"]
end
def find_package_version(package_name)
return nil unless File.exist?(package_json_path)

parsed = parsed_package_json
return nil unless parsed

parsed.dig("dependencies", package_name) || parsed.dig("devDependencies", package_name)
end

def parsed_package_json
return @parsed_package_json if defined?(@parsed_package_json)

@parsed_package_json = begin
JSON.parse(File.read(package_json_path))
rescue JSON::ParserError, Errno::ENOENT
nil
end
msg = "No 'react-on-rails' entry in the dependencies of #{NodePackageVersion.package_json_path}, " \
"which is the expected location according to ReactOnRails.configuration.node_modules_location"
Rails.logger.warn(msg)
@raw = nil
end
end

# Represents a package version string from package.json
class PackageVersion
attr_reader :raw

def initialize(raw)
@raw = raw
end

def semver_wildcard?
Expand All @@ -114,12 +241,6 @@ def parts

match.captures.compact
end

private

def package_json_contents
@package_json_contents ||= File.read(package_json)
end
end
end
end
4 changes: 3 additions & 1 deletion react_on_rails_pro/lib/react_on_rails_pro/version.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
# frozen_string_literal: true

module ReactOnRailsPro
VERSION = "4.0.0"
# Version is now synchronized with main react_on_rails gem
VERSION = "16.1.1"
# Protocol version remains independent - only changes when communication protocol changes
PROTOCOL_VERSION = "2.0.0"
end
2 changes: 1 addition & 1 deletion react_on_rails_pro/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@shakacode-tools/react-on-rails-pro-node-renderer",
"version": "4.0.0",
"version": "16.1.1",
"protocolVersion": "2.0.0",
"description": "react-on-rails-pro JavaScript for react_on_rails_pro Ruby gem",
"exports": {
Expand Down
7 changes: 4 additions & 3 deletions react_on_rails_pro/react_on_rails_pro.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,11 @@ Gem::Specification.new do |s|
s.metadata["rubygems_mfa_required"] = "true"

s.files = `git ls-files -z`.split("\x0")
.reject { |f|
.reject do |f|
f.match(
%r{^(test|spec|features|tmp|node_modules|packages|coverage|Gemfile.lock|lib/tasks)/}
)
}
end
s.bindir = "exe"
s.executables = s.files.grep(%r{^exe/}) { |f| File.basename(f) }
s.require_paths = ["lib"]
Expand All @@ -34,7 +34,8 @@ Gem::Specification.new do |s|
s.add_runtime_dependency "httpx", "~> 1.5"
s.add_runtime_dependency "jwt", "~> 2.7"
s.add_runtime_dependency "rainbow"
s.add_runtime_dependency "react_on_rails", ">= 16.0.0"
# Pro gem requires exact version match with main gem for guaranteed compatibility
s.add_runtime_dependency "react_on_rails", ReactOnRailsPro::VERSION
s.add_development_dependency "bundler"
s.add_development_dependency "commonmarker"
s.add_development_dependency "gem-release"
Expand Down
Loading