@@ -4,63 +4,153 @@ module ReactOnRails
44 # Responsible for checking versions of rubygem versus yarn node package
55 # against each other at runtime.
66 class VersionChecker
7- attr_reader :node_package_version
7+ attr_reader :package_json_data
88
99 # Semver uses - to separate pre-release, but RubyGems use .
1010 VERSION_PARTS_REGEX = /(\d +)\. (\d +)\. (\d +)(?:[-.]([0-9A-Za-z.-]+))?/
1111
1212 def self . build
13- new ( NodePackageVersion . build )
13+ new ( PackageJsonData . build )
1414 end
1515
16- def initialize ( node_package_version )
17- @node_package_version = node_package_version
16+ def initialize ( package_json_data )
17+ @package_json_data = package_json_data
1818 end
1919
2020 # For compatibility, the gem and the node package versions should always match,
2121 # unless the user really knows what they're doing. So we will give a
2222 # warning if they do not.
2323 def log_if_gem_and_node_package_versions_differ
24- return if node_package_version . raw . nil? || node_package_version . local_path_or_url?
25- return log_node_semver_version_warning if node_package_version . semver_wildcard?
24+ errors = [ ]
2625
27- log_differing_versions_warning unless node_package_version . parts == gem_version_parts
26+ # Check if both react-on-rails and react-on-rails-pro are present
27+ check_for_both_packages ( errors )
28+
29+ # Check react-on-rails or react-on-rails-pro package
30+ check_main_package_version ( errors )
31+
32+ # Check node-renderer package if Pro is present
33+ check_node_renderer_version ( errors ) if package_json_data . pro_package?
34+
35+ # Handle errors based on environment
36+ handle_errors ( errors ) if errors . any?
2837 end
2938
3039 private
3140
32- def common_error_msg
33- <<-MSG . strip_heredoc
34- Detected: #{ node_package_version . raw }
35- gem: #{ gem_version }
36- Ensure the installed version of the gem is the same as the version of
37- your installed Node package. Do not use >= or ~> in your Gemfile for react_on_rails.
38- Do not use ^, ~, or other non-exact versions in your package.json for react-on-rails.
39- Run `yarn add react-on-rails --exact` in the directory containing folder node_modules.
41+ def check_for_both_packages ( errors )
42+ return unless package_json_data . both_packages?
43+
44+ msg = <<~MSG . strip_heredoc
45+ React on Rails: Both 'react-on-rails' and 'react-on-rails-pro' packages are detected in package.json.
46+ You only need to install 'react-on-rails-pro' package as it already includes 'react-on-rails' as a dependency.
47+ Please remove 'react-on-rails' from your package.json dependencies.
48+ MSG
49+ errors << { type : :warning , message : msg }
50+ end
51+
52+ def check_main_package_version ( errors )
53+ package_name = package_json_data . pro_package? ? "react-on-rails-pro" : "react-on-rails"
54+ package_version_data = package_json_data . get_package_version ( package_name )
55+
56+ return if package_version_data . nil?
57+ return if package_version_data . local_path_or_url?
58+
59+ # Check for exact version (no semver wildcards)
60+ if package_version_data . semver_wildcard?
61+ msg = build_semver_wildcard_error ( package_name , package_version_data . raw )
62+ errors << { type : :error , message : msg }
63+ return
64+ end
65+
66+ # Check version match
67+ expected_version = package_json_data . pro_package? ? pro_gem_version : gem_version
68+ return if package_version_data . parts == version_parts ( expected_version )
69+
70+ msg = build_version_mismatch_error ( package_name , package_version_data . raw , expected_version )
71+ errors << { type : :error , message : msg }
72+ end
73+
74+ def check_node_renderer_version ( errors )
75+ node_renderer_data = package_json_data . get_package_version ( "@shakacode-tools/react-on-rails-pro-node-renderer" )
76+ return if node_renderer_data . nil?
77+ return if node_renderer_data . local_path_or_url?
78+
79+ # Check for exact version
80+ if node_renderer_data . semver_wildcard?
81+ msg = build_semver_wildcard_error ( "@shakacode-tools/react-on-rails-pro-node-renderer" ,
82+ node_renderer_data . raw )
83+ errors << { type : :error , message : msg }
84+ return
85+ end
86+
87+ # Check version match with Pro gem
88+ return if node_renderer_data . parts == version_parts ( pro_gem_version )
89+
90+ msg = build_version_mismatch_error ( "@shakacode-tools/react-on-rails-pro-node-renderer" ,
91+ node_renderer_data . raw , pro_gem_version )
92+ errors << { type : :error , message : msg }
93+ end
94+
95+ def build_semver_wildcard_error ( package_name , raw_version )
96+ <<~MSG . strip_heredoc
97+ React on Rails: Package '#{ package_name } ' is using a non-exact version: #{ raw_version }
98+ For guaranteed compatibility, you must use exact versions (no ^, ~, >=, etc.).
99+ Run: yarn add #{ package_name } @#{ expected_version_for_package ( package_name ) } --exact
100+ MSG
101+ end
102+
103+ def build_version_mismatch_error ( package_name , package_version , gem_version )
104+ <<~MSG . strip_heredoc
105+ React on Rails: Package '#{ package_name } ' version does not match the gem version.
106+ Package version: #{ package_version }
107+ Gem version: #{ gem_version }
108+ Run: yarn add #{ package_name } @#{ gem_version } --exact
40109 MSG
41110 end
42111
43- def log_differing_versions_warning
44- msg = "**WARNING** ReactOnRails: ReactOnRails gem and Node package versions do not match\n #{ common_error_msg } "
45- Rails . logger . warn ( msg )
112+ def expected_version_for_package ( package_name )
113+ case package_name
114+ when "react-on-rails"
115+ gem_version
116+ when "react-on-rails-pro" , "@shakacode-tools/react-on-rails-pro-node-renderer"
117+ pro_gem_version
118+ end
46119 end
47120
48- def log_node_semver_version_warning
49- msg = "**WARNING** ReactOnRails: Your Node package version for react-on-rails is not an exact version\n " \
50- "#{ common_error_msg } "
51- Rails . logger . warn ( msg )
121+ def handle_errors ( errors )
122+ errors . each do |error |
123+ if error [ :type ] == :warning
124+ Rails . logger . warn ( "**WARNING** #{ error [ :message ] } " )
125+ elsif development_or_test?
126+ raise ReactOnRails ::Error , error [ :message ]
127+ else
128+ Rails . logger . error ( "**ERROR** #{ error [ :message ] } " )
129+ end
130+ end
131+ end
132+
133+ def development_or_test?
134+ Rails . env . development? || Rails . env . test?
52135 end
53136
54137 def gem_version
55138 ReactOnRails ::VERSION
56139 end
57140
58- def gem_version_parts
59- gem_version . match ( VERSION_PARTS_REGEX ) &.captures &.compact
141+ def pro_gem_version
142+ return nil unless defined? ( ReactOnRailsPro )
143+
144+ ReactOnRailsPro ::VERSION
60145 end
61146
62- class NodePackageVersion
63- attr_reader :package_json
147+ def version_parts ( version )
148+ version &.match ( VERSION_PARTS_REGEX ) &.captures &.compact
149+ end
150+
151+ # Represents package.json data and provides methods to check for packages
152+ class PackageJsonData
153+ attr_reader :package_json_path
64154
65155 def self . build
66156 new ( package_json_path )
@@ -70,24 +160,61 @@ def self.package_json_path
70160 Rails . root . join ( ReactOnRails . configuration . node_modules_location , "package.json" )
71161 end
72162
73- def initialize ( package_json )
74- @package_json = package_json
163+ def initialize ( package_json_path )
164+ @package_json_path = package_json_path
165+ end
166+
167+ def pro_package?
168+ package_exists? ( "react-on-rails-pro" )
169+ end
170+
171+ def main_package?
172+ package_exists? ( "react-on-rails" )
173+ end
174+
175+ def both_packages?
176+ main_package? && pro_package?
177+ end
178+
179+ def get_package_version ( package_name )
180+ version = find_package_version ( package_name )
181+ return nil if version . nil?
182+
183+ PackageVersion . new ( version )
75184 end
76185
77- def raw
78- return @raw if defined? ( @raw )
186+ private
187+
188+ def package_exists? ( package_name )
189+ !find_package_version ( package_name ) . nil?
190+ end
79191
80- if File . exist? ( package_json )
81- parsed_package_contents = JSON . parse ( package_json_contents )
82- if parsed_package_contents . key? ( "dependencies" ) &&
83- parsed_package_contents [ "dependencies" ] . key? ( "react-on-rails" )
84- return @raw = parsed_package_contents [ "dependencies" ] [ "react-on-rails" ]
85- end
192+ def find_package_version ( package_name )
193+ return nil unless File . exist? ( package_json_path )
194+
195+ parsed = parsed_package_json
196+ return nil unless parsed
197+
198+ parsed . dig ( "dependencies" , package_name ) || parsed . dig ( "devDependencies" , package_name )
199+ end
200+
201+ def parsed_package_json
202+ return @parsed_package_json if defined? ( @parsed_package_json )
203+
204+ @parsed_package_json = begin
205+ JSON . parse ( File . read ( package_json_path ) )
206+ rescue JSON ::ParserError , Errno ::ENOENT
207+ nil
86208 end
87- msg = "No 'react-on-rails' entry in the dependencies of #{ NodePackageVersion . package_json_path } , " \
88- "which is the expected location according to ReactOnRails.configuration.node_modules_location"
89- Rails . logger . warn ( msg )
90- @raw = nil
209+ end
210+ end
211+
212+ # Represents a package version string from package.json
213+ class PackageVersion
214+ attr_reader :raw
215+
216+ def initialize ( raw )
217+ @raw = raw
91218 end
92219
93220 def semver_wildcard?
@@ -114,12 +241,6 @@ def parts
114241
115242 match . captures . compact
116243 end
117-
118- private
119-
120- def package_json_contents
121- @package_json_contents ||= File . read ( package_json )
122- end
123244 end
124245 end
125246end
0 commit comments