Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 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
6 changes: 5 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,11 @@ Please follow the recommendations outlined at [keepachangelog.com](http://keepac
### [Unreleased]
Changes since the last non-beta release.

#### Added(https://github.com/AbanoubGhadban).
#### Fixed

- Changed the ReactOnRails' version checker to check package.jsons in both root and client directories. [PR 1657](https://github.com/shakacode/react_on_rails/pull/1657) by [judahmeek](https://github.com/judahmeek).
Copy link
Member

Choose a reason for hiding this comment

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


#### Added
- Added streaming server rendering support:
- [PR #1633](https://github.com/shakacode/react_on_rails/pull/1633) by [AbanoubGhadban](https://github.com/AbanoubGhadban).
- New `stream_react_component` helper for adding streamed components to views
Expand Down
5 changes: 3 additions & 2 deletions lib/react_on_rails/engine.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,9 @@
module ReactOnRails
class Engine < ::Rails::Engine
config.to_prepare do
if File.exist?(VersionChecker::NodePackageVersion.package_json_path)
VersionChecker.build.raise_if_gem_and_node_package_versions_differ
if VersionChecker.instance("package.json").log_if_gem_and_node_package_versions_differ &&
VersionChecker.instance("client/package.json").log_if_gem_and_node_package_versions_differ
Rails.logger.warn("No 'react-on-rails' entry found in 'dependencies' in package.json or client/package.json.")
Copy link
Member

Choose a reason for hiding this comment

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

  1. Isn't this a breaking change?
  2. Semantically, having methods with side effect of raise does not seem right for an if

Copy link
Member

Choose a reason for hiding this comment

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

all this consistency checking logic should go to one method, that logs warnings for now, and will throw for the next major version bump

end
ReactOnRails::ServerRenderingPool.reset_pool
end
Expand Down
58 changes: 45 additions & 13 deletions lib/react_on_rails/version_checker.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@ def self.build
new(NodePackageVersion.build)
end

def self.instance(package_json_path)
Copy link
Member

Choose a reason for hiding this comment

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

do we need the arg?

can we just use node_modules_location ?

new(NodePackageVersion.new(package_json_path))
end
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Consider adding parameter validation for package_json_path

The new instance method accepts a path parameter without validation. This could lead to issues if invalid paths are provided.

 def self.instance(package_json_path)
+  raise ReactOnRails::Error, "package_json_path cannot be nil" if package_json_path.nil?
+  raise ReactOnRails::Error, "package_json_path must be a string" unless package_json_path.is_a?(String)
   new(NodePackageVersion.new(package_json_path))
 end
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
def self.instance(package_json_path)
new(NodePackageVersion.new(package_json_path))
end
def self.instance(package_json_path)
raise ReactOnRails::Error, "package_json_path cannot be nil" if package_json_path.nil?
raise ReactOnRails::Error, "package_json_path must be a string" unless package_json_path.is_a?(String)
new(NodePackageVersion.new(package_json_path))
end


def initialize(node_package_version)
@node_package_version = node_package_version
end
Expand All @@ -20,21 +24,37 @@ def initialize(node_package_version)
# unless the user really knows what they're doing. So we will give a
# warning if they do not.
def raise_if_gem_and_node_package_versions_differ
Copy link
Member

Choose a reason for hiding this comment

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

is anybody code still calling this one?

we'll need a reminder for the next big update? Maybe leave a PR pending?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

We can create a follow-up PR and leave it pending.

return true unless node_package_version.raw
return if node_package_version.relative_path?

node_major_minor_patch = node_package_version.major_minor_patch
gem_major_minor_patch = gem_major_minor_patch_version
versions_match = node_major_minor_patch[0] == gem_major_minor_patch[0] &&
node_major_minor_patch[1] == gem_major_minor_patch[1] &&
node_major_minor_patch[2] == gem_major_minor_patch[2]
raise_node_semver_version_warning if node_package_version.semver_wildcard?

versions_match = compare_versions(node_package_version.major_minor_patch, gem_major_minor_patch_version)

raise_differing_versions_warning unless versions_match
false
end

raise_node_semver_version_warning if node_package_version.semver_wildcard?
def log_if_gem_and_node_package_versions_differ
return true unless node_package_version.raw
return if node_package_version.relative_path?

log_node_semver_version_warning if node_package_version.semver_wildcard?

versions_match = compare_versions(node_package_version.major_minor_patch, gem_major_minor_patch_version)

log_differing_versions_warning unless versions_match
false
Copy link
Contributor

@coderabbitai coderabbitai bot Dec 5, 2024

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Consider refactoring to reduce duplication and improve naming.

The raise_if_gem_and_node_package_versions_differ and log_if_gem_and_node_package_versions_differ methods have identical logic but different actions. Consider:

  1. Extracting the common logic into a private method
  2. Renaming methods to better reflect their behavior (they return boolean values)
-def raise_if_gem_and_node_package_versions_differ
-  return true unless node_package_version.raw
-  return if node_package_version.relative_path?
-
-  raise_node_semver_version_warning if node_package_version.semver_wildcard?
-
-  versions_match = compare_versions(node_package_version.major_minor_patch, gem_major_minor_patch_version)
-
-  raise_differing_versions_warning unless versions_match
-  false
-end
-
-def log_if_gem_and_node_package_versions_differ
-  return true unless node_package_version.raw
-  return if node_package_version.relative_path?
-
-  log_node_semver_version_warning if node_package_version.semver_wildcard?
-
-  versions_match = compare_versions(node_package_version.major_minor_patch, gem_major_minor_patch_version)
-
-  log_differing_versions_warning unless versions_match
-  false
-end
+def check_versions(action = :log)
+  return true unless node_package_version.raw
+  return if node_package_version.relative_path?
+
+  if node_package_version.semver_wildcard?
+    action == :log ? log_node_semver_version_warning : raise_node_semver_version_warning
+  end
+
+  versions_match = compare_versions(node_package_version.major_minor_patch, gem_major_minor_patch_version)
+
+  unless versions_match
+    action == :log ? log_differing_versions_warning : raise_differing_versions_warning
+  end
+  false
+end
+
+def check_versions_with_raise
+  check_versions(:raise)
+end
+
+def check_versions_with_log
+  check_versions(:log)
+end
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
return true unless node_package_version.raw
return if node_package_version.relative_path?
node_major_minor_patch = node_package_version.major_minor_patch
gem_major_minor_patch = gem_major_minor_patch_version
versions_match = node_major_minor_patch[0] == gem_major_minor_patch[0] &&
node_major_minor_patch[1] == gem_major_minor_patch[1] &&
node_major_minor_patch[2] == gem_major_minor_patch[2]
raise_node_semver_version_warning if node_package_version.semver_wildcard?
versions_match = compare_versions(node_package_version.major_minor_patch, gem_major_minor_patch_version)
raise_differing_versions_warning unless versions_match
false
end
raise_node_semver_version_warning if node_package_version.semver_wildcard?
def log_if_gem_and_node_package_versions_differ
return true unless node_package_version.raw
return if node_package_version.relative_path?
log_node_semver_version_warning if node_package_version.semver_wildcard?
versions_match = compare_versions(node_package_version.major_minor_patch, gem_major_minor_patch_version)
log_differing_versions_warning unless versions_match
false
def check_versions(action = :log)
return true unless node_package_version.raw
return if node_package_version.relative_path?
if node_package_version.semver_wildcard?
action == :log ? log_node_semver_version_warning : raise_node_semver_version_warning
end
versions_match = compare_versions(node_package_version.major_minor_patch, gem_major_minor_patch_version)
unless versions_match
action == :log ? log_differing_versions_warning : raise_differing_versions_warning
end
false
end
def check_versions_with_raise
check_versions(:raise)
end
def check_versions_with_log
check_versions(:log)
end

Copy link
Member

Choose a reason for hiding this comment

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

@Judahmeek please commit this this suggestion.

Copy link
Contributor

Choose a reason for hiding this comment

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

Seems like the humans are having a chat. I'll hop back into my burrow for now. If you need me again, just tag @coderabbitai in a new comment, and I'll come hopping out!

end

private

def compare_versions(node_major_minor_patch, gem_major_minor_patch)
node_major_minor_patch[0] == gem_major_minor_patch[0] &&
node_major_minor_patch[1] == gem_major_minor_patch[1] &&
node_major_minor_patch[2] == gem_major_minor_patch[2]
end

def common_error_msg
<<-MSG.strip_heredoc
Detected: #{node_package_version.raw}
Expand All @@ -43,20 +63,32 @@ def common_error_msg
your installed node package. Do not use >= or ~> in your Gemfile for react_on_rails.
Do not use ^ or ~ in your package.json for react-on-rails.
Run `yarn add react-on-rails --exact` in the directory containing folder node_modules.
***This warning will become a fatal error in ReactOnRails v15***
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Review warning message implementation

The current implementation:

  1. Always displays verbose warning messages
  2. Includes a note about fatal errors in v15, which might cause unnecessary concern
  3. Logs warnings for both version mismatches and semver wildcards

Consider:

  1. Making warning messages configurable or less verbose
  2. Removing the v15 warning if it's not immediately relevant
  3. Adding a debug log level for detailed information

Also applies to: 53-54, 58-60

MSG
end

def raise_differing_versions_warning
Copy link
Member

Choose a reason for hiding this comment

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

method names can't be raise, but mean log

msg = "**ERROR** ReactOnRails: ReactOnRails gem and node package versions do not match\n#{common_error_msg}"
msg = "**WARNING** ReactOnRails: ReactOnRails gem and node package versions do not match\n#{common_error_msg}"
raise ReactOnRails::Error, msg
end

def raise_node_semver_version_warning
msg = "**ERROR** ReactOnRails: Your node package version for react-on-rails contains a " \
msg = "**WARNING** ReactOnRails: Your node package version for react-on-rails contains a " \
"^ or ~\n#{common_error_msg}"
raise ReactOnRails::Error, 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)
end

def log_node_semver_version_warning
msg = "**WARNING** ReactOnRails: Your node package version for react-on-rails contains a " \
"^ or ~\n#{common_error_msg}"
Rails.logger.warn(msg)
end

def gem_version
ReactOnRails::VERSION
end
Expand All @@ -73,21 +105,21 @@ def self.build
new(package_json_path)
end

def self.package_json_path
Rails.root.join("client", "package.json")
def self.package_json_path(relative_path = "package.json")
Rails.root.join(relative_path)
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Add path validation for package.json

The method accepts any relative path without validation. Consider adding checks to:

  1. Ensure the path ends with 'package.json'
  2. Validate that the path doesn't escape the Rails root directory
 def self.package_json_path(relative_path = "package.json")
+  unless relative_path.end_with?("package.json")
+    raise ReactOnRails::Error, "Path must end with package.json"
+  end
+  path = Rails.root.join(relative_path)
+  unless path.to_s.start_with?(Rails.root.to_s)
+    raise ReactOnRails::Error, "Path must be within Rails root directory"
+  end
-  Rails.root.join(relative_path)
+  path
 end
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
def self.package_json_path(relative_path = "package.json")
Rails.root.join(relative_path)
def self.package_json_path(relative_path = "package.json")
unless relative_path.end_with?("package.json")
raise ReactOnRails::Error, "Path must end with package.json"
end
path = Rails.root.join(relative_path)
unless path.to_s.start_with?(Rails.root.to_s)
raise ReactOnRails::Error, "Path must be within Rails root directory"
end
path

end

def initialize(package_json)
@package_json = package_json
end

def raw
return nil unless 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")
parsed_package_contents["dependencies"]["react-on-rails"]
else
raise ReactOnRails::Error, "No 'react-on-rails' entry in package.json dependencies"
end
end

Expand All @@ -96,7 +128,7 @@ def semver_wildcard?
end

def relative_path?
raw.match(%r{(\.\.|\Afile:///)}).present?
raw.match(/(\.\.|\Afile:)/).present?
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

Security: Strengthen relative path detection

The current regex allows potentially dangerous path patterns:

  1. .. could allow directory traversal
  2. file: protocol could allow access to arbitrary files

Consider using a more restrictive approach:

-  raw.match(/(\.\.|\Afile:)/).present?
+  raw.start_with?('file:./') || raw.start_with?('file:src/')

Committable suggestion skipped: line range outside the PR's diff.

end

def major_minor_patch
Expand Down
10 changes: 10 additions & 0 deletions spec/react_on_rails/fixtures/yalc_package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"dependencies": {
"babel": "^6.3.26",
"react-on-rails": "file:.yalc/react-on-rails",
"webpack": "^1.12.8"
},
"devDependencies": {
"babel-eslint": "^5.0.0-beta6"
}
}
113 changes: 100 additions & 13 deletions spec/react_on_rails/version_checker_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ def error(message)
end
end

module ReactOnRails
module ReactOnRails # rubocop:disable Metrics/ModuleLength
describe VersionChecker do
describe "#warn_if_gem_and_node_package_versions_differ" do
let(:logger) { FakeLogger.new }
Expand All @@ -24,7 +24,13 @@ module ReactOnRails
before { stub_gem_version("2.2.5.beta.2") }

it "does not raise" do
expect { check_version(node_package_version) }.not_to raise_error
expect { check_version_and_raise(node_package_version) }.not_to raise_error
end

it "does not log" do
allow(Rails.logger).to receive(:warn)
check_version_and_log(node_package_version)
expect(Rails.logger).not_to have_received(:warn)
end
end

Expand All @@ -35,9 +41,17 @@ module ReactOnRails

before { stub_gem_version("2.2.5") }

it "does raise" do
error = /ReactOnRails: Your node package version for react-on-rails contains a \^ or ~/
expect { check_version(node_package_version) }.to raise_error(error)
it "logs" do
allow(Rails.logger).to receive(:warn)
message = /ReactOnRails: Your node package version for react-on-rails contains a \^ or ~/
check_version_and_log(node_package_version)
expect(Rails.logger).to have_received(:warn).with(message)
end

it "raises" do
allow(Rails.logger).to receive(:warn)
message = /ReactOnRails: Your node package version for react-on-rails contains a \^ or ~/
expect { check_version_and_raise(node_package_version) }.to raise_error(message)
end
end

Expand All @@ -48,9 +62,17 @@ module ReactOnRails

before { stub_gem_version("12.0.0.beta.1") }

it "logs" do
allow(Rails.logger).to receive(:warn)
message = /ReactOnRails: ReactOnRails gem and node package versions do not match/
check_version_and_log(node_package_version)
expect(Rails.logger).to have_received(:warn).with(message)
end

it "raises" do
error = /ReactOnRails: ReactOnRails gem and node package versions do not match/
expect { check_version(node_package_version) }.to raise_error(error)
allow(Rails.logger).to receive(:warn)
message = /ReactOnRails: ReactOnRails gem and node package versions do not match/
expect { check_version_and_raise(node_package_version) }.to raise_error(message)
end
end

Expand All @@ -61,9 +83,17 @@ module ReactOnRails

before { stub_gem_version("13.1.0") }

it "logs" do
allow(Rails.logger).to receive(:warn)
message = /ReactOnRails: ReactOnRails gem and node package versions do not match/
check_version_and_log(node_package_version)
expect(Rails.logger).to have_received(:warn).with(message)
end

it "raises" do
error = /ReactOnRails: ReactOnRails gem and node package versions do not match/
expect { check_version(node_package_version) }.to raise_error(error)
allow(Rails.logger).to receive(:warn)
message = /ReactOnRails: ReactOnRails gem and node package versions do not match/
expect { check_version_and_raise(node_package_version) }.to raise_error(message)
end
end

Expand All @@ -74,9 +104,17 @@ module ReactOnRails

before { stub_gem_version("13.0.0") }

it "logs" do
allow(Rails.logger).to receive(:warn)
message = /ReactOnRails: ReactOnRails gem and node package versions do not match/
check_version_and_log(node_package_version)
expect(Rails.logger).to have_received(:warn).with(message)
end

it "raises" do
error = /ReactOnRails: ReactOnRails gem and node package versions do not match/
expect { check_version(node_package_version) }.to raise_error(error)
allow(Rails.logger).to receive(:warn)
message = /ReactOnRails: ReactOnRails gem and node package versions do not match/
expect { check_version_and_raise(node_package_version) }.to raise_error(message)
end
end

Expand All @@ -88,7 +126,27 @@ module ReactOnRails
before { stub_gem_version("2.0.0.beta.1") }

it "does not raise" do
expect { check_version(node_package_version) }.not_to raise_error
expect { check_version_and_raise(node_package_version) }.not_to raise_error
end

it "does not log" do
allow(Rails.logger).to receive(:warn)
check_version_and_log(node_package_version)
expect(Rails.logger).not_to have_received(:warn)
end
end

context "when package json doesn't exist" do
let(:node_package_version) do
double_package_version(raw: nil)
end

it "raise method returns true" do
expect(check_version_and_raise(node_package_version)).to be(true)
end

it "log method returns true" do
expect(check_version_and_log(node_package_version)).to be(true)
end
end
end
Expand All @@ -102,11 +160,16 @@ def double_package_version(raw: nil, semver_wildcard: false,
relative_path?: relative_path)
end

def check_version(node_package_version)
def check_version_and_raise(node_package_version)
version_checker = VersionChecker.new(node_package_version)
version_checker.raise_if_gem_and_node_package_versions_differ
end

def check_version_and_log(node_package_version)
version_checker = VersionChecker.new(node_package_version)
version_checker.log_if_gem_and_node_package_versions_differ
end

describe VersionChecker::NodePackageVersion do
subject(:node_package_version) { described_class.new(package_json) }

Expand Down Expand Up @@ -193,6 +256,30 @@ def check_version(node_package_version)
specify { expect(node_package_version.major_minor_patch).to be_nil }
end
end

context "with node version of 'file:.yalc/react-on-rails'" do
let(:package_json) { File.expand_path("fixtures/yalc_package.json", __dir__) }

describe "#raw" do
specify { expect(node_package_version.raw).to eq("file:.yalc/react-on-rails") }
end

describe "#relative_path?" do
specify { expect(node_package_version.relative_path?).to be true }
end

describe "#major" do
specify { expect(node_package_version.major_minor_patch).to be_nil }
end
end

context "with non-existing package.json" do
let(:package_json) { File.expand_path("fixtures/nonexistent_package.json", __dir__) }

describe "#raw" do
specify { expect(node_package_version.raw).to be_nil }
end
end
end
end
end
Loading