react_on_rails 16.1.2 → 16.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (106) hide show
  1. checksums.yaml +4 -4
  2. data/.rspec +2 -0
  3. data/.rubocop.yml +85 -0
  4. data/Gemfile.development_dependencies +8 -7
  5. data/Gemfile.lock +158 -119
  6. data/Steepfile +56 -0
  7. data/lib/generators/react_on_rails/base_generator.rb +43 -120
  8. data/lib/generators/react_on_rails/dev_tests_generator.rb +2 -1
  9. data/lib/generators/react_on_rails/generator_helper.rb +102 -2
  10. data/lib/generators/react_on_rails/install_generator.rb +36 -156
  11. data/lib/generators/react_on_rails/js_dependency_manager.rb +383 -0
  12. data/lib/generators/react_on_rails/templates/base/base/.dev-services.yml.example +76 -0
  13. data/lib/generators/react_on_rails/templates/base/base/bin/shakapacker-precompile-hook +30 -0
  14. data/lib/generators/react_on_rails/templates/base/base/bin/switch-bundler +141 -0
  15. data/lib/generators/react_on_rails/templates/base/base/config/initializers/react_on_rails.rb.tt +44 -45
  16. data/lib/generators/react_on_rails/templates/base/base/config/{shakapacker.yml → shakapacker.yml.tt} +28 -3
  17. data/lib/generators/react_on_rails/templates/base/base/config/webpack/development.js.tt +15 -9
  18. data/lib/generators/react_on_rails/templates/base/base/config/webpack/serverWebpackConfig.js.tt +42 -6
  19. data/lib/react_on_rails/configuration.rb +149 -32
  20. data/lib/react_on_rails/controller.rb +3 -3
  21. data/lib/react_on_rails/dev/pack_generator.rb +168 -2
  22. data/lib/react_on_rails/dev/process_manager.rb +136 -14
  23. data/lib/react_on_rails/dev/server_manager.rb +194 -26
  24. data/lib/react_on_rails/dev/service_checker.rb +200 -0
  25. data/lib/react_on_rails/doctor.rb +341 -12
  26. data/lib/react_on_rails/engine.rb +75 -1
  27. data/lib/react_on_rails/git_utils.rb +3 -1
  28. data/lib/react_on_rails/helper.rb +70 -192
  29. data/lib/react_on_rails/locales/base.rb +17 -5
  30. data/lib/react_on_rails/packer_utils.rb +79 -2
  31. data/lib/react_on_rails/packs_generator.rb +57 -39
  32. data/lib/react_on_rails/prerender_error.rb +74 -17
  33. data/lib/react_on_rails/pro_helper.rb +64 -0
  34. data/lib/react_on_rails/react_component/render_options.rb +7 -7
  35. data/lib/react_on_rails/server_rendering_pool/ruby_embedded_java_script.rb +2 -5
  36. data/lib/react_on_rails/smart_error.rb +326 -0
  37. data/lib/react_on_rails/system_checker.rb +8 -9
  38. data/lib/react_on_rails/test_helper/webpack_assets_status_checker.rb +16 -7
  39. data/lib/react_on_rails/utils.rb +241 -55
  40. data/lib/react_on_rails/version.rb +1 -1
  41. data/lib/react_on_rails/version_checker.rb +383 -35
  42. data/lib/tasks/generate_packs.rake +12 -6
  43. data/lib/tasks/locale.rake +6 -1
  44. data/rakelib/docker.rake +26 -0
  45. data/rakelib/dummy_apps.rake +30 -0
  46. data/rakelib/example_type.rb +121 -0
  47. data/rakelib/examples_config.yml +52 -0
  48. data/rakelib/lint.rake +52 -0
  49. data/rakelib/node_package.rake +15 -0
  50. data/rakelib/rbs.rake +70 -0
  51. data/rakelib/run_rspec.rake +223 -0
  52. data/rakelib/shakapacker_examples.rake +171 -0
  53. data/rakelib/task_helpers.rb +134 -0
  54. data/rakelib/update_changelog.rake +73 -0
  55. data/react_on_rails.gemspec +4 -3
  56. data/sig/README.md +52 -0
  57. data/sig/react_on_rails/configuration.rbs +96 -0
  58. data/sig/react_on_rails/controller.rbs +15 -0
  59. data/sig/react_on_rails/dev/file_manager.rbs +15 -0
  60. data/sig/react_on_rails/dev/pack_generator.rbs +19 -0
  61. data/sig/react_on_rails/dev/process_manager.rbs +22 -0
  62. data/sig/react_on_rails/dev/server_manager.rbs +39 -0
  63. data/sig/react_on_rails/dev/service_checker.rbs +22 -0
  64. data/sig/react_on_rails/error.rbs +4 -0
  65. data/sig/react_on_rails/generators/js_dependency_manager.rbs +123 -0
  66. data/sig/react_on_rails/git_utils.rbs +8 -0
  67. data/sig/react_on_rails/helper.rbs +65 -0
  68. data/sig/react_on_rails/json_parse_error.rbs +10 -0
  69. data/sig/react_on_rails/locales.rbs +46 -0
  70. data/sig/react_on_rails/packer_utils.rbs +15 -0
  71. data/sig/react_on_rails/prerender_error.rbs +21 -0
  72. data/sig/react_on_rails/server_rendering_pool.rbs +12 -0
  73. data/sig/react_on_rails/smart_error.rbs +28 -0
  74. data/sig/react_on_rails/test_helper.rbs +11 -0
  75. data/sig/react_on_rails/utils.rbs +34 -0
  76. data/sig/react_on_rails/version_checker.rbs +12 -0
  77. data/sig/react_on_rails.rbs +17 -0
  78. metadata +49 -32
  79. data/AI_AGENT_INSTRUCTIONS.md +0 -63
  80. data/CHANGELOG.md +0 -1836
  81. data/CLAUDE.md +0 -135
  82. data/CODING_AGENTS.md +0 -313
  83. data/CONTRIBUTING.md +0 -668
  84. data/Dockerfile_tests +0 -12
  85. data/KUDOS.md +0 -114
  86. data/LICENSE.md +0 -47
  87. data/LICENSES/README.md +0 -14
  88. data/NEWS.md +0 -62
  89. data/PROJECTS.md +0 -63
  90. data/REACT-ON-RAILS-PRO-LICENSE.md +0 -129
  91. data/README.md +0 -217
  92. data/SUMMARY.md +0 -88
  93. data/TODO.md +0 -135
  94. data/bin/lefthook/check-trailing-newlines +0 -38
  95. data/bin/lefthook/get-changed-files +0 -26
  96. data/bin/lefthook/prettier-format +0 -26
  97. data/bin/lefthook/ruby-autofix +0 -26
  98. data/bin/lefthook/ruby-lint +0 -27
  99. data/docker-compose.yml +0 -11
  100. data/eslint.config.ts +0 -232
  101. data/knip.ts +0 -114
  102. data/lib/react_on_rails/pro/NOTICE +0 -21
  103. data/lib/react_on_rails/pro/helper.rb +0 -122
  104. data/lib/react_on_rails/pro/utils.rb +0 -53
  105. data/tsconfig.eslint.json +0 -6
  106. data/tsconfig.json +0 -19
@@ -17,38 +17,187 @@ module ReactOnRails
17
17
  @node_package_version = node_package_version
18
18
  end
19
19
 
20
- # For compatibility, the gem and the node package versions should always match,
21
- # unless the user really knows what they're doing. So we will give a
22
- # warning if they do not.
23
- 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?
26
-
27
- log_differing_versions_warning unless node_package_version.parts == gem_version_parts
20
+ # Validates version and package compatibility.
21
+ # Raises ReactOnRails::Error if:
22
+ # - package.json file is not found
23
+ # - Both react-on-rails and react-on-rails-pro packages are installed
24
+ # - Pro gem is installed but using react-on-rails package
25
+ # - Pro package is installed but Pro gem is not installed
26
+ # - Non-exact version is used
27
+ # - Versions don't match
28
+ def validate_version_and_package_compatibility!
29
+ validate_package_json_exists!
30
+ validate_package_gem_compatibility!
31
+ validate_exact_version!
32
+ validate_version_match!
28
33
  end
29
34
 
30
35
  private
31
36
 
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.
37
+ def validate_package_json_exists!
38
+ return if File.exist?(node_package_version.package_json)
39
+
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
+
43
+ raise ReactOnRails::Error, <<~MSG.strip
44
+ **ERROR** ReactOnRails: package.json file not found.
45
+
46
+ Expected location: #{node_package_version.package_json}
47
+
48
+ React on Rails requires a package.json file with either 'react-on-rails' or
49
+ 'react-on-rails-pro' package installed.
50
+
51
+ Fix:
52
+ 1. Ensure you have a package.json in your project root
53
+ 2. Run: #{base_install_cmd}
54
+
55
+ Or if using React on Rails Pro:
56
+ Run: #{pro_install_cmd}
40
57
  MSG
41
58
  end
42
59
 
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)
60
+ def validate_package_gem_compatibility!
61
+ has_base_package = node_package_version.react_on_rails_package?
62
+ has_pro_package = node_package_version.react_on_rails_pro_package?
63
+ is_pro_gem = ReactOnRails::Utils.react_on_rails_pro?
64
+
65
+ validate_packages_installed!(has_base_package, has_pro_package)
66
+ validate_no_duplicate_packages!(has_base_package, has_pro_package)
67
+ validate_pro_gem_uses_pro_package!(is_pro_gem, has_pro_package)
68
+ validate_pro_package_has_pro_gem!(is_pro_gem, has_pro_package)
46
69
  end
47
70
 
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)
71
+ def validate_packages_installed!(has_base_package, has_pro_package)
72
+ return if has_base_package || has_pro_package
73
+
74
+ base_install_cmd = ReactOnRails::Utils.package_manager_install_exact_command("react-on-rails", gem_version)
75
+ pro_install_cmd = ReactOnRails::Utils.package_manager_install_exact_command("react-on-rails-pro", gem_version)
76
+
77
+ raise ReactOnRails::Error, <<~MSG.strip
78
+ **ERROR** ReactOnRails: No React on Rails npm package is installed.
79
+
80
+ You must install either 'react-on-rails' or 'react-on-rails-pro' package.
81
+
82
+ Fix:
83
+ If using the standard (free) version:
84
+ Run: #{base_install_cmd}
85
+
86
+ Or if using React on Rails Pro:
87
+ Run: #{pro_install_cmd}
88
+
89
+ #{package_json_location}
90
+ MSG
91
+ end
92
+
93
+ def validate_no_duplicate_packages!(has_base_package, has_pro_package)
94
+ return unless has_base_package && has_pro_package
95
+
96
+ remove_cmd = ReactOnRails::Utils.package_manager_remove_command("react-on-rails")
97
+
98
+ raise ReactOnRails::Error, <<~MSG.strip
99
+ **ERROR** ReactOnRails: Both 'react-on-rails' and 'react-on-rails-pro' packages are installed.
100
+
101
+ If you're using React on Rails Pro, only install the 'react-on-rails-pro' package.
102
+ The Pro package already includes all functionality from the base package.
103
+
104
+ Fix:
105
+ 1. Remove 'react-on-rails' from your package.json dependencies
106
+ 2. Run: #{remove_cmd}
107
+ 3. Keep only: react-on-rails-pro
108
+
109
+ #{package_json_location}
110
+ MSG
111
+ end
112
+
113
+ def validate_pro_gem_uses_pro_package!(is_pro_gem, has_pro_package)
114
+ return unless is_pro_gem && !has_pro_package
115
+
116
+ remove_cmd = ReactOnRails::Utils.package_manager_remove_command("react-on-rails")
117
+ install_cmd = ReactOnRails::Utils.package_manager_install_exact_command("react-on-rails-pro", gem_version)
118
+
119
+ raise ReactOnRails::Error, <<~MSG.strip
120
+ **ERROR** ReactOnRails: You have the Pro gem installed but are using the base 'react-on-rails' package.
121
+
122
+ When using React on Rails Pro, you must use the 'react-on-rails-pro' npm package.
123
+
124
+ Fix:
125
+ 1. Remove the base package: #{remove_cmd}
126
+ 2. Install the Pro package: #{install_cmd}
127
+
128
+ #{package_json_location}
129
+ MSG
130
+ end
131
+
132
+ def validate_pro_package_has_pro_gem!(is_pro_gem, has_pro_package)
133
+ return unless !is_pro_gem && has_pro_package
134
+
135
+ remove_pro_cmd = ReactOnRails::Utils.package_manager_remove_command("react-on-rails-pro")
136
+ install_base_cmd = ReactOnRails::Utils.package_manager_install_exact_command("react-on-rails", gem_version)
137
+
138
+ raise ReactOnRails::Error, <<~MSG.strip
139
+ **ERROR** ReactOnRails: You have the 'react-on-rails-pro' package installed but the Pro gem is not installed.
140
+
141
+ The Pro npm package requires the Pro gem to function.
142
+
143
+ Fix:
144
+ 1. Install the Pro gem by adding to your Gemfile:
145
+ gem 'react_on_rails_pro'
146
+ 2. Run: bundle install
147
+
148
+ Or if you meant to use the base version:
149
+ 1. Remove the Pro package: #{remove_pro_cmd}
150
+ 2. Install the base package: #{install_base_cmd}
151
+
152
+ #{package_json_location}
153
+ MSG
154
+ end
155
+
156
+ def validate_exact_version!
157
+ return if node_package_version.raw.nil? || node_package_version.local_path_or_url?
158
+
159
+ return unless node_package_version.semver_wildcard?
160
+
161
+ package_name = node_package_version.package_name
162
+ install_cmd = ReactOnRails::Utils.package_manager_install_exact_command(package_name, gem_version)
163
+
164
+ raise ReactOnRails::Error, <<~MSG.strip
165
+ **ERROR** ReactOnRails: The '#{package_name}' package version is not an exact version.
166
+
167
+ Detected: #{node_package_version.raw}
168
+ Gem: #{gem_version}
169
+
170
+ React on Rails requires exact version matching between the gem and npm package.
171
+ Do not use ^, ~, >, <, *, or other semver ranges.
172
+
173
+ Fix:
174
+ Run: #{install_cmd}
175
+
176
+ #{package_json_location}
177
+ MSG
178
+ end
179
+
180
+ def validate_version_match!
181
+ return if node_package_version.raw.nil? || node_package_version.local_path_or_url?
182
+
183
+ return if node_package_version.parts == gem_version_parts
184
+
185
+ package_name = node_package_version.package_name
186
+ install_cmd = ReactOnRails::Utils.package_manager_install_exact_command(package_name, gem_version)
187
+
188
+ raise ReactOnRails::Error, <<~MSG.strip
189
+ **ERROR** ReactOnRails: The '#{package_name}' package version does not match the gem version.
190
+
191
+ Package: #{node_package_version.raw}
192
+ Gem: #{gem_version}
193
+
194
+ The npm package and gem versions must match exactly for compatibility.
195
+
196
+ Fix:
197
+ Run: #{install_cmd}
198
+
199
+ #{package_json_location}
200
+ MSG
52
201
  end
53
202
 
54
203
  def gem_version
@@ -59,42 +208,113 @@ module ReactOnRails
59
208
  gem_version.match(VERSION_PARTS_REGEX)&.captures&.compact
60
209
  end
61
210
 
211
+ def package_json_location
212
+ "Package.json location: #{VersionChecker::NodePackageVersion.package_json_path}"
213
+ end
214
+
215
+ # rubocop:disable Metrics/ClassLength
62
216
  class NodePackageVersion
63
- attr_reader :package_json
217
+ attr_reader :package_json, :yarn_lock, :package_lock
64
218
 
65
219
  def self.build
66
- new(package_json_path)
220
+ new(package_json_path, yarn_lock_path, package_lock_path)
67
221
  end
68
222
 
69
223
  def self.package_json_path
70
224
  Rails.root.join(ReactOnRails.configuration.node_modules_location, "package.json")
71
225
  end
72
226
 
73
- def initialize(package_json)
227
+ def self.yarn_lock_path
228
+ # Lockfiles are in the same directory as package.json
229
+ # If node_modules_location is empty, use Rails.root
230
+ base_dir = ReactOnRails.configuration.node_modules_location.presence || ""
231
+ Rails.root.join(base_dir, "yarn.lock").to_s
232
+ end
233
+
234
+ def self.package_lock_path
235
+ # Lockfiles are in the same directory as package.json
236
+ # If node_modules_location is empty, use Rails.root
237
+ base_dir = ReactOnRails.configuration.node_modules_location.presence || ""
238
+ Rails.root.join(base_dir, "package-lock.json").to_s
239
+ end
240
+
241
+ def initialize(package_json, yarn_lock = nil, package_lock = nil)
74
242
  @package_json = package_json
243
+ @yarn_lock = yarn_lock
244
+ @package_lock = package_lock
75
245
  end
76
246
 
77
247
  def raw
78
248
  return @raw if defined?(@raw)
79
249
 
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
250
+ return @raw = nil unless File.exist?(package_json)
251
+
252
+ parsed = parsed_package_contents
253
+ return @raw = nil unless parsed.key?("dependencies")
254
+
255
+ deps = parsed["dependencies"]
256
+
257
+ # Check for react-on-rails-pro first (Pro takes precedence)
258
+ if deps.key?("react-on-rails-pro")
259
+ @raw = resolve_version(deps["react-on-rails-pro"], "react-on-rails-pro")
260
+ return @raw
86
261
  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"
262
+
263
+ # Fall back to react-on-rails
264
+ if deps.key?("react-on-rails")
265
+ @raw = resolve_version(deps["react-on-rails"], "react-on-rails")
266
+ return @raw
267
+ end
268
+
269
+ # Neither package found
270
+ msg = "No 'react-on-rails' or 'react-on-rails-pro' entry in the dependencies of " \
271
+ "#{NodePackageVersion.package_json_path}, which is the expected location according to " \
272
+ "ReactOnRails.configuration.node_modules_location"
89
273
  Rails.logger.warn(msg)
90
274
  @raw = nil
91
275
  end
92
276
 
277
+ def react_on_rails_package?
278
+ package_installed?("react-on-rails")
279
+ end
280
+
281
+ def react_on_rails_pro_package?
282
+ package_installed?("react-on-rails-pro")
283
+ end
284
+
285
+ def package_name
286
+ return "react-on-rails-pro" if react_on_rails_pro_package?
287
+
288
+ "react-on-rails"
289
+ end
290
+
93
291
  def semver_wildcard?
94
292
  # See https://docs.npmjs.com/cli/v10/configuring-npm/package-json#dependencies
95
293
  # We want to disallow all expressions other than exact versions
96
294
  # and the ones allowed by local_path_or_url?
97
- raw.blank? || raw.start_with?(/[~^><*]/) || raw.include?(" - ") || raw.include?(" || ")
295
+ return true if raw.blank?
296
+
297
+ special_version_string? || wildcard_or_x_range? || range_operator? || range_syntax?
298
+ end
299
+
300
+ def special_version_string?
301
+ %w[latest next canary beta alpha rc].include?(raw.downcase)
302
+ end
303
+
304
+ def wildcard_or_x_range?
305
+ raw == "*" ||
306
+ raw =~ /^[xX*]$/ ||
307
+ raw =~ /^[xX*]\./ ||
308
+ raw =~ /\.[xX*]\b/ ||
309
+ raw =~ /\.[xX*]$/
310
+ end
311
+
312
+ def range_operator?
313
+ raw.start_with?(/[~^><*]/)
314
+ end
315
+
316
+ def range_syntax?
317
+ raw.include?(" - ") || raw.include?(" || ")
98
318
  end
99
319
 
100
320
  def local_path_or_url?
@@ -117,9 +337,137 @@ module ReactOnRails
117
337
 
118
338
  private
119
339
 
340
+ # Resolve version from lockfiles if available, otherwise use package.json version
341
+ def resolve_version(package_json_version, package_name)
342
+ # If package.json specifies a local path or URL, don't try to resolve from lockfiles
343
+ # Lockfiles may contain placeholder versions like "0.0.0" for local links
344
+ return package_json_version if local_path_or_url_version?(package_json_version)
345
+
346
+ # Try yarn.lock first
347
+ if yarn_lock && File.exist?(yarn_lock)
348
+ lockfile_version = version_from_yarn_lock(package_name)
349
+ return lockfile_version if lockfile_version
350
+ end
351
+
352
+ # Try package-lock.json
353
+ if package_lock && File.exist?(package_lock)
354
+ lockfile_version = version_from_package_lock(package_name)
355
+ return lockfile_version if lockfile_version
356
+ end
357
+
358
+ # Fall back to package.json version
359
+ package_json_version
360
+ end
361
+
362
+ # Check if a version string represents a local path or URL
363
+ def local_path_or_url_version?(version)
364
+ return false if version.nil?
365
+
366
+ version.include?("/") && !version.start_with?("npm:")
367
+ end
368
+
369
+ # Parse version from yarn.lock
370
+ # Looks for entries like:
371
+ # react-on-rails@^16.1.1:
372
+ # version "16.1.1"
373
+ # The pattern ensures exact package name match to avoid matching similar names
374
+ # (e.g., "react-on-rails" won't match "react-on-rails-pro")
375
+ # rubocop:disable Metrics/CyclomaticComplexity
376
+ def version_from_yarn_lock(package_name)
377
+ return nil unless yarn_lock && File.exist?(yarn_lock)
378
+
379
+ in_package_block = false
380
+ File.foreach(yarn_lock) do |line|
381
+ # Check if we're starting the block for our package
382
+ # Pattern: optionally quoted package name, followed by @, ensuring it's not followed by more word chars
383
+ # This prevents "react-on-rails" from matching "react-on-rails-pro"
384
+ if line.match?(/^"?#{Regexp.escape(package_name)}@/)
385
+ in_package_block = true
386
+ next
387
+ end
388
+
389
+ # If we're in the package block, look for the version line
390
+ if in_package_block
391
+ # Version line looks like: version "16.1.1"
392
+ if (match = line.match(/^\s+version\s+"([^"]+)"/))
393
+ return match[1]
394
+ end
395
+
396
+ # If we hit a blank line or new package, we've left the block
397
+ break if line.strip.empty? || (line[0] != " " && line[0] != "\t")
398
+ end
399
+ end
400
+
401
+ nil
402
+ end
403
+ # rubocop:enable Metrics/CyclomaticComplexity
404
+
405
+ # Parse version from package-lock.json
406
+ # Supports both v1 (dependencies) and v2/v3 (packages) formats
407
+ # rubocop:disable Metrics/CyclomaticComplexity
408
+ def version_from_package_lock(package_name)
409
+ return nil unless package_lock && File.exist?(package_lock)
410
+
411
+ begin
412
+ parsed = JSON.parse(File.read(package_lock))
413
+
414
+ # Try v2/v3 format first (packages)
415
+ if parsed["packages"]
416
+ # Look for node_modules/package-name entry
417
+ node_modules_key = "node_modules/#{package_name}"
418
+ package_data = parsed["packages"][node_modules_key]
419
+ return package_data["version"] if package_data&.key?("version")
420
+ end
421
+
422
+ # Fall back to v1 format (dependencies)
423
+ if parsed["dependencies"]
424
+ dependency_data = parsed["dependencies"][package_name]
425
+ # In v1, the dependency can be a hash with a "version" key
426
+ return dependency_data["version"] if dependency_data.is_a?(Hash) && dependency_data.key?("version")
427
+ end
428
+ rescue JSON::ParserError
429
+ # If we can't parse the lockfile, fall back to package.json version
430
+ nil
431
+ end
432
+
433
+ nil
434
+ end
435
+ # rubocop:enable Metrics/CyclomaticComplexity
436
+
437
+ def package_installed?(package_name)
438
+ return false unless File.exist?(package_json)
439
+
440
+ parsed = parsed_package_contents
441
+ parsed.dig("dependencies", package_name).present?
442
+ end
443
+
120
444
  def package_json_contents
121
445
  @package_json_contents ||= File.read(package_json)
122
446
  end
447
+
448
+ def parsed_package_contents
449
+ return @parsed_package_contents if defined?(@parsed_package_contents)
450
+
451
+ begin
452
+ @parsed_package_contents = JSON.parse(package_json_contents)
453
+ rescue JSON::ParserError => e
454
+ raise ReactOnRails::Error, <<~MSG.strip
455
+ **ERROR** ReactOnRails: Failed to parse package.json file.
456
+
457
+ Location: #{package_json}
458
+ Error: #{e.message}
459
+
460
+ The package.json file contains invalid JSON. Please check the file for syntax errors.
461
+
462
+ Common issues:
463
+ - Missing or extra commas
464
+ - Unquoted keys or values
465
+ - Trailing commas (not allowed in JSON)
466
+ - Comments (not allowed in standard JSON)
467
+ MSG
468
+ end
469
+ end
123
470
  end
471
+ # rubocop:enable Metrics/ClassLength
124
472
  end
125
473
  end
@@ -17,18 +17,24 @@ namespace :react_on_rails do
17
17
  DESC
18
18
 
19
19
  task generate_packs: :environment do
20
- puts Rainbow("🚀 Starting React on Rails pack generation...").bold
21
- puts Rainbow("📁 Auto-load bundle: #{ReactOnRails.configuration.auto_load_bundle}").cyan
22
- puts Rainbow("📂 Components subdirectory: #{ReactOnRails.configuration.components_subdirectory}").cyan
23
- puts ""
20
+ verbose = ENV["REACT_ON_RAILS_VERBOSE"] == "true"
21
+
22
+ if verbose
23
+ puts Rainbow("🚀 Starting React on Rails pack generation...").bold
24
+ puts Rainbow("📁 Auto-load bundle: #{ReactOnRails.configuration.auto_load_bundle}").cyan
25
+ puts Rainbow("📂 Components subdirectory: #{ReactOnRails.configuration.components_subdirectory}").cyan
26
+ puts ""
27
+ end
24
28
 
25
29
  begin
26
30
  start_time = Time.now
27
31
  ReactOnRails::PacksGenerator.instance.generate_packs_if_stale
28
32
  end_time = Time.now
29
33
 
30
- puts ""
31
- puts Rainbow("✨ Pack generation completed in #{((end_time - start_time) * 1000).round(1)}ms").green
34
+ if verbose
35
+ puts ""
36
+ puts Rainbow("✨ Pack generation completed in #{((end_time - start_time) * 1000).round(1)}ms").green
37
+ end
32
38
  rescue ReactOnRails::Error => e
33
39
  handle_react_on_rails_error(e)
34
40
  exit 1
@@ -9,8 +9,13 @@ namespace :react_on_rails do
9
9
  Generate i18n javascript files
10
10
  This task generates javascript locale files: `translations.js` & `default.js` and places them in
11
11
  the "ReactOnRails.configuration.i18n_dir".
12
+
13
+ Options:
14
+ force=true - Force regeneration even if files are up to date
15
+ Example: rake react_on_rails:locale force=true
12
16
  DESC
13
17
  task locale: :environment do
14
- ReactOnRails::Locales.compile
18
+ force = %w[true 1 yes].include?(ENV["force"]&.downcase)
19
+ ReactOnRails::Locales.compile(force: force)
15
20
  end
16
21
  end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ namespace :docker do
4
+ desc "Run Rubocop linter from docker"
5
+ task :rubocop do
6
+ sh "docker-compose run lint rake lint:rubocop"
7
+ end
8
+
9
+ desc "Run stylelint linter from docker"
10
+ task :scss do
11
+ sh "docker-compose run lint rake lint:scss"
12
+ end
13
+
14
+ desc "Run eslint linter from docker"
15
+ task :eslint do
16
+ sh "docker-compose run lint rake lint:eslint"
17
+ end
18
+
19
+ desc "Run all linting from docker"
20
+ task :lint do
21
+ sh "docker-compose run lint rake lint"
22
+ end
23
+ end
24
+
25
+ desc "Runs all linters from docker. Run `rake -D docker` to see all available lint options"
26
+ task docker: ["docker:lint"]
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "task_helpers"
4
+
5
+ namespace :dummy_apps do
6
+ include ReactOnRails::TaskHelpers
7
+
8
+ task :pnpm_install do
9
+ pnpm_install_cmd = "pnpm install"
10
+ sh_in_dir(dummy_app_dir, pnpm_install_cmd)
11
+ sh_in_dir(dummy_app_dir, "yalc link react-on-rails")
12
+ end
13
+
14
+ task dummy_app: [:pnpm_install] do
15
+ dummy_app_dir = File.join(gem_root, "spec/dummy")
16
+ bundle_install_in(dummy_app_dir)
17
+ end
18
+
19
+ task :generate_packs do
20
+ dummy_app_dir = File.join(gem_root, "spec/dummy")
21
+ sh_in_dir(dummy_app_dir, "bundle exec rake react_on_rails:generate_packs")
22
+ end
23
+
24
+ task dummy_apps: %i[dummy_app node_package generate_packs] do
25
+ puts "Prepared all Dummy Apps"
26
+ end
27
+ end
28
+
29
+ desc "Prepares all dummy apps by installing dependencies"
30
+ task dummy_apps: ["dummy_apps:dummy_apps"]