react_on_rails 16.0.0 → 16.0.1.rc.2
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +124 -77
- data/CLAUDE.md +46 -2
- data/CONTRIBUTING.md +12 -6
- data/Gemfile.development_dependencies +1 -0
- data/Gemfile.lock +3 -1
- data/LICENSE.md +15 -1
- data/README.md +68 -18
- data/bin/lefthook/check-trailing-newlines +38 -0
- data/bin/lefthook/get-changed-files +26 -0
- data/bin/lefthook/prettier-format +26 -0
- data/bin/lefthook/ruby-autofix +26 -0
- data/bin/lefthook/ruby-lint +27 -0
- data/eslint.config.ts +10 -0
- data/knip.ts +20 -9
- data/lib/generators/react_on_rails/USAGE +65 -0
- data/lib/generators/react_on_rails/base_generator.rb +7 -7
- data/lib/generators/react_on_rails/generator_helper.rb +4 -0
- data/lib/generators/react_on_rails/generator_messages.rb +2 -2
- data/lib/generators/react_on_rails/install_generator.rb +115 -7
- data/lib/generators/react_on_rails/react_no_redux_generator.rb +16 -4
- data/lib/generators/react_on_rails/react_with_redux_generator.rb +83 -14
- data/lib/generators/react_on_rails/templates/base/base/app/javascript/src/HelloWorld/ror_components/HelloWorld.client.tsx +25 -0
- data/lib/generators/react_on_rails/templates/base/base/app/javascript/src/HelloWorld/ror_components/HelloWorld.server.tsx +5 -0
- data/lib/generators/react_on_rails/templates/base/base/bin/dev +12 -24
- data/lib/generators/react_on_rails/templates/redux/base/app/javascript/bundles/HelloWorld/actions/helloWorldActionCreators.ts +18 -0
- data/lib/generators/react_on_rails/templates/redux/base/app/javascript/bundles/HelloWorld/components/HelloWorld.tsx +24 -0
- data/lib/generators/react_on_rails/templates/redux/base/app/javascript/bundles/HelloWorld/constants/helloWorldConstants.ts +6 -0
- data/lib/generators/react_on_rails/templates/redux/base/app/javascript/bundles/HelloWorld/containers/HelloWorldContainer.ts +20 -0
- data/lib/generators/react_on_rails/templates/redux/base/app/javascript/bundles/HelloWorld/reducers/helloWorldReducer.ts +22 -0
- data/lib/generators/react_on_rails/templates/redux/base/app/javascript/bundles/HelloWorld/startup/HelloWorldApp.client.tsx +23 -0
- data/lib/generators/react_on_rails/templates/redux/base/app/javascript/bundles/HelloWorld/startup/HelloWorldApp.server.tsx +5 -0
- data/lib/generators/react_on_rails/templates/redux/base/app/javascript/bundles/HelloWorld/store/helloWorldStore.ts +18 -0
- data/lib/react_on_rails/configuration.rb +10 -6
- data/lib/react_on_rails/dev/server_manager.rb +185 -28
- data/lib/react_on_rails/doctor.rb +1149 -0
- data/lib/react_on_rails/helper.rb +9 -78
- data/lib/react_on_rails/pro/NOTICE +21 -0
- data/lib/react_on_rails/pro/helper.rb +122 -0
- data/lib/react_on_rails/pro/utils.rb +53 -0
- data/lib/react_on_rails/react_component/render_options.rb +6 -2
- data/lib/react_on_rails/system_checker.rb +659 -0
- data/lib/react_on_rails/version.rb +1 -1
- data/lib/tasks/doctor.rake +48 -0
- data/lib/tasks/generate_packs.rake +127 -4
- data/package-lock.json +11984 -0
- metadata +26 -6
- data/lib/generators/react_on_rails/bin/dev +0 -46
@@ -0,0 +1,659 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "open3"
|
4
|
+
|
5
|
+
module ReactOnRails
|
6
|
+
# SystemChecker provides validation methods for React on Rails setup
|
7
|
+
# Used by install generator and doctor rake task
|
8
|
+
# rubocop:disable Metrics/ClassLength
|
9
|
+
class SystemChecker
|
10
|
+
attr_reader :messages
|
11
|
+
|
12
|
+
def initialize
|
13
|
+
@messages = []
|
14
|
+
end
|
15
|
+
|
16
|
+
def add_error(message)
|
17
|
+
@messages << { type: :error, content: message }
|
18
|
+
end
|
19
|
+
|
20
|
+
def add_warning(message)
|
21
|
+
@messages << { type: :warning, content: message }
|
22
|
+
end
|
23
|
+
|
24
|
+
def add_success(message)
|
25
|
+
@messages << { type: :success, content: message }
|
26
|
+
end
|
27
|
+
|
28
|
+
def add_info(message)
|
29
|
+
@messages << { type: :info, content: message }
|
30
|
+
end
|
31
|
+
|
32
|
+
def errors?
|
33
|
+
@messages.any? { |msg| msg[:type] == :error }
|
34
|
+
end
|
35
|
+
|
36
|
+
def warnings?
|
37
|
+
@messages.any? { |msg| msg[:type] == :warning }
|
38
|
+
end
|
39
|
+
|
40
|
+
# Node.js validation
|
41
|
+
def check_node_installation
|
42
|
+
if node_missing?
|
43
|
+
add_error(<<~MSG.strip)
|
44
|
+
🚫 Node.js is required but not found on your system.
|
45
|
+
|
46
|
+
Please install Node.js before continuing:
|
47
|
+
• Download from: https://nodejs.org/en/
|
48
|
+
• Recommended: Use a version manager like nvm, fnm, or volta
|
49
|
+
• Minimum required version: Node.js 18+
|
50
|
+
|
51
|
+
After installation, restart your terminal and try again.
|
52
|
+
MSG
|
53
|
+
return false
|
54
|
+
end
|
55
|
+
|
56
|
+
check_node_version
|
57
|
+
true
|
58
|
+
end
|
59
|
+
|
60
|
+
def check_node_version
|
61
|
+
stdout, stderr, status = Open3.capture3("node", "--version")
|
62
|
+
|
63
|
+
# Use stdout if available, fallback to stderr if stdout is empty
|
64
|
+
node_version = stdout.strip
|
65
|
+
node_version = stderr.strip if node_version.empty?
|
66
|
+
|
67
|
+
# Return early if node is not found (non-zero status) or no output
|
68
|
+
return if !status.success? || node_version.empty?
|
69
|
+
|
70
|
+
# Extract major version number (e.g., "v18.17.0" -> 18)
|
71
|
+
major_version = node_version[/v(\d+)/, 1]&.to_i
|
72
|
+
return unless major_version
|
73
|
+
|
74
|
+
if major_version < 18
|
75
|
+
add_warning(<<~MSG.strip)
|
76
|
+
⚠️ Node.js version #{node_version} detected.
|
77
|
+
|
78
|
+
React on Rails recommends Node.js 18+ for best compatibility.
|
79
|
+
You may experience issues with older versions.
|
80
|
+
|
81
|
+
Consider upgrading: https://nodejs.org/en/
|
82
|
+
MSG
|
83
|
+
else
|
84
|
+
add_success("✅ Node.js #{node_version} is installed and compatible")
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
# Package manager validation
|
89
|
+
def check_package_manager
|
90
|
+
package_managers = %w[npm pnpm yarn bun]
|
91
|
+
available_managers = package_managers.select { |pm| cli_exists?(pm) }
|
92
|
+
|
93
|
+
if available_managers.empty?
|
94
|
+
add_error(<<~MSG.strip)
|
95
|
+
🚫 No JavaScript package manager found on your system.
|
96
|
+
|
97
|
+
React on Rails requires a JavaScript package manager to install dependencies.
|
98
|
+
Please install one of the following:
|
99
|
+
|
100
|
+
• npm: Usually comes with Node.js (https://nodejs.org/en/)
|
101
|
+
• yarn: npm install -g yarn (https://yarnpkg.com/)
|
102
|
+
• pnpm: npm install -g pnpm (https://pnpm.io/)
|
103
|
+
• bun: Install from https://bun.sh/
|
104
|
+
|
105
|
+
After installation, restart your terminal and try again.
|
106
|
+
MSG
|
107
|
+
return false
|
108
|
+
end
|
109
|
+
|
110
|
+
# Detect which package manager is actually being used
|
111
|
+
used_manager = detect_used_package_manager
|
112
|
+
if used_manager
|
113
|
+
version_info = get_package_manager_version(used_manager)
|
114
|
+
deprecation_note = get_deprecation_note(used_manager, version_info)
|
115
|
+
message = "✅ Package manager in use: #{used_manager} #{version_info}"
|
116
|
+
message += deprecation_note if deprecation_note
|
117
|
+
add_success(message)
|
118
|
+
else
|
119
|
+
add_success("✅ Package managers available: #{available_managers.join(', ')}")
|
120
|
+
add_info("ℹ️ No lock file detected - run npm/yarn/pnpm install to establish which manager is used")
|
121
|
+
end
|
122
|
+
true
|
123
|
+
end
|
124
|
+
|
125
|
+
# Shakapacker validation
|
126
|
+
def check_shakapacker_configuration
|
127
|
+
unless shakapacker_configured?
|
128
|
+
add_error(<<~MSG.strip)
|
129
|
+
🚫 Shakapacker is not properly configured.
|
130
|
+
|
131
|
+
Missing one or more required files:
|
132
|
+
• bin/shakapacker
|
133
|
+
• bin/shakapacker-dev-server
|
134
|
+
• config/shakapacker.yml
|
135
|
+
• config/webpack/webpack.config.js
|
136
|
+
|
137
|
+
Run: bundle exec rails shakapacker:install
|
138
|
+
MSG
|
139
|
+
return false
|
140
|
+
end
|
141
|
+
|
142
|
+
report_shakapacker_version_with_threshold
|
143
|
+
check_shakapacker_in_gemfile
|
144
|
+
true
|
145
|
+
end
|
146
|
+
|
147
|
+
def check_shakapacker_in_gemfile
|
148
|
+
if shakapacker_in_gemfile?
|
149
|
+
add_success("✅ Shakapacker is declared in Gemfile")
|
150
|
+
else
|
151
|
+
add_warning(<<~MSG.strip)
|
152
|
+
⚠️ Shakapacker not found in Gemfile.
|
153
|
+
|
154
|
+
While Shakapacker might be available as a dependency,
|
155
|
+
it's recommended to add it explicitly to your Gemfile:
|
156
|
+
|
157
|
+
bundle add shakapacker --strict
|
158
|
+
MSG
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
# React on Rails package validation
|
163
|
+
def check_react_on_rails_packages
|
164
|
+
check_react_on_rails_gem
|
165
|
+
check_react_on_rails_npm_package
|
166
|
+
check_package_version_sync
|
167
|
+
check_gemfile_version_patterns
|
168
|
+
end
|
169
|
+
|
170
|
+
def check_react_on_rails_gem
|
171
|
+
require "react_on_rails"
|
172
|
+
add_success("✅ React on Rails gem #{ReactOnRails::VERSION} is loaded")
|
173
|
+
rescue LoadError
|
174
|
+
add_error(<<~MSG.strip)
|
175
|
+
🚫 React on Rails gem is not available.
|
176
|
+
|
177
|
+
Add to your Gemfile:
|
178
|
+
gem 'react_on_rails'
|
179
|
+
|
180
|
+
Then run: bundle install
|
181
|
+
MSG
|
182
|
+
end
|
183
|
+
|
184
|
+
def check_react_on_rails_npm_package
|
185
|
+
package_json_path = "package.json"
|
186
|
+
return unless File.exist?(package_json_path)
|
187
|
+
|
188
|
+
package_json = JSON.parse(File.read(package_json_path))
|
189
|
+
npm_version = package_json.dig("dependencies", "react-on-rails") ||
|
190
|
+
package_json.dig("devDependencies", "react-on-rails")
|
191
|
+
|
192
|
+
if npm_version
|
193
|
+
add_success("✅ react-on-rails NPM package #{npm_version} is declared")
|
194
|
+
else
|
195
|
+
add_warning(<<~MSG.strip)
|
196
|
+
⚠️ react-on-rails NPM package not found in package.json.
|
197
|
+
|
198
|
+
Install it with:
|
199
|
+
npm install react-on-rails
|
200
|
+
MSG
|
201
|
+
end
|
202
|
+
rescue JSON::ParserError
|
203
|
+
add_warning("⚠️ Could not parse package.json")
|
204
|
+
end
|
205
|
+
|
206
|
+
def check_package_version_sync # rubocop:disable Metrics/CyclomaticComplexity
|
207
|
+
return unless File.exist?("package.json")
|
208
|
+
|
209
|
+
begin
|
210
|
+
package_json = JSON.parse(File.read("package.json"))
|
211
|
+
npm_version = package_json.dig("dependencies", "react-on-rails") ||
|
212
|
+
package_json.dig("devDependencies", "react-on-rails")
|
213
|
+
|
214
|
+
return unless npm_version && defined?(ReactOnRails::VERSION)
|
215
|
+
|
216
|
+
# Clean version strings for comparison (remove ^, ~, =, etc.)
|
217
|
+
clean_npm_version = npm_version.gsub(/[^0-9.]/, "")
|
218
|
+
gem_version = ReactOnRails::VERSION
|
219
|
+
|
220
|
+
if clean_npm_version == gem_version
|
221
|
+
add_success("✅ React on Rails gem and NPM package versions match (#{gem_version})")
|
222
|
+
check_version_patterns(npm_version, gem_version)
|
223
|
+
else
|
224
|
+
# Check for major version differences
|
225
|
+
gem_major = gem_version.split(".")[0].to_i
|
226
|
+
npm_major = clean_npm_version.split(".")[0].to_i
|
227
|
+
|
228
|
+
if gem_major != npm_major # rubocop:disable Style/NegatedIfElseCondition
|
229
|
+
add_error(<<~MSG.strip)
|
230
|
+
🚫 Major version mismatch detected:
|
231
|
+
• Gem version: #{gem_version} (major: #{gem_major})
|
232
|
+
• NPM version: #{npm_version} (major: #{npm_major})
|
233
|
+
|
234
|
+
Major version differences can cause serious compatibility issues.
|
235
|
+
Update both packages to use the same major version immediately.
|
236
|
+
MSG
|
237
|
+
else
|
238
|
+
add_warning(<<~MSG.strip)
|
239
|
+
⚠️ Version mismatch detected:
|
240
|
+
• Gem version: #{gem_version}
|
241
|
+
• NPM version: #{npm_version}
|
242
|
+
|
243
|
+
Consider updating to exact, fixed matching versions of gem and npm package for best compatibility.
|
244
|
+
MSG
|
245
|
+
end
|
246
|
+
end
|
247
|
+
rescue JSON::ParserError
|
248
|
+
# Ignore parsing errors, already handled elsewhere
|
249
|
+
rescue StandardError
|
250
|
+
# Handle other errors gracefully
|
251
|
+
end
|
252
|
+
end
|
253
|
+
|
254
|
+
# React dependencies validation
|
255
|
+
def check_react_dependencies
|
256
|
+
return unless File.exist?("package.json")
|
257
|
+
|
258
|
+
package_json = parse_package_json
|
259
|
+
return unless package_json
|
260
|
+
|
261
|
+
# Check core React dependencies
|
262
|
+
required_deps = required_react_dependencies
|
263
|
+
missing_deps = find_missing_dependencies(package_json, required_deps)
|
264
|
+
report_dependency_status(required_deps, missing_deps, package_json)
|
265
|
+
|
266
|
+
# Check additional build dependencies (informational)
|
267
|
+
check_build_dependencies(package_json)
|
268
|
+
|
269
|
+
# Report versions
|
270
|
+
report_dependency_versions(package_json)
|
271
|
+
end
|
272
|
+
|
273
|
+
# Rails integration validation
|
274
|
+
|
275
|
+
def check_react_on_rails_initializer
|
276
|
+
initializer_path = "config/initializers/react_on_rails.rb"
|
277
|
+
if File.exist?(initializer_path)
|
278
|
+
add_success("✅ React on Rails initializer exists")
|
279
|
+
else
|
280
|
+
add_warning(<<~MSG.strip)
|
281
|
+
⚠️ React on Rails initializer not found.
|
282
|
+
|
283
|
+
Create: config/initializers/react_on_rails.rb
|
284
|
+
Or run: rails generate react_on_rails:install
|
285
|
+
MSG
|
286
|
+
end
|
287
|
+
end
|
288
|
+
|
289
|
+
# Webpack configuration validation
|
290
|
+
def check_webpack_configuration
|
291
|
+
webpack_config_path = "config/webpack/webpack.config.js"
|
292
|
+
if File.exist?(webpack_config_path)
|
293
|
+
add_success("✅ Webpack configuration exists")
|
294
|
+
check_webpack_config_content
|
295
|
+
suggest_webpack_inspection
|
296
|
+
else
|
297
|
+
add_error(<<~MSG.strip)
|
298
|
+
🚫 Webpack configuration not found.
|
299
|
+
|
300
|
+
Expected: config/webpack/webpack.config.js
|
301
|
+
Run: rails generate react_on_rails:install
|
302
|
+
MSG
|
303
|
+
end
|
304
|
+
end
|
305
|
+
|
306
|
+
def suggest_webpack_inspection
|
307
|
+
add_info("💡 To debug webpack builds:")
|
308
|
+
add_info(" bin/shakapacker --mode=development --progress")
|
309
|
+
add_info(" bin/shakapacker --mode=production --progress")
|
310
|
+
add_info(" bin/shakapacker --debug-shakapacker # Debug Shakapacker configuration")
|
311
|
+
|
312
|
+
add_info("💡 Advanced webpack debugging:")
|
313
|
+
add_info(" 1. Add 'debugger;' before 'module.exports' in config/webpack/webpack.config.js")
|
314
|
+
add_info(" 2. Run: ./bin/shakapacker --debug-shakapacker")
|
315
|
+
add_info(" 3. Open Chrome DevTools to inspect config object")
|
316
|
+
add_info(" 📖 See: https://github.com/shakacode/shakapacker/blob/main/docs/troubleshooting.md#debugging-your-webpack-config")
|
317
|
+
|
318
|
+
add_info("💡 To analyze bundle size:")
|
319
|
+
if bundle_analyzer_available?
|
320
|
+
add_info(" ANALYZE=true bin/shakapacker")
|
321
|
+
add_info(" This opens webpack-bundle-analyzer in your browser")
|
322
|
+
else
|
323
|
+
add_info(" 1. yarn add --dev webpack-bundle-analyzer")
|
324
|
+
add_info(" 2. Add to config/webpack/webpack.config.js:")
|
325
|
+
add_info(" const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer');")
|
326
|
+
add_info(" // Add to plugins array when process.env.ANALYZE")
|
327
|
+
add_info(" 3. ANALYZE=true bin/shakapacker")
|
328
|
+
add_info(" Or use Shakapacker's built-in support if available")
|
329
|
+
end
|
330
|
+
|
331
|
+
add_info("💡 Generate webpack stats for analysis:")
|
332
|
+
add_info(" bin/shakapacker --json > webpack-stats.json")
|
333
|
+
add_info(" Upload to webpack.github.io/analyse or webpack-bundle-analyzer.com")
|
334
|
+
end
|
335
|
+
|
336
|
+
def bundle_analyzer_available?
|
337
|
+
return false unless File.exist?("package.json")
|
338
|
+
|
339
|
+
begin
|
340
|
+
package_json = JSON.parse(File.read("package.json"))
|
341
|
+
all_deps = package_json["dependencies"]&.merge(package_json["devDependencies"] || {}) || {}
|
342
|
+
all_deps["webpack-bundle-analyzer"]
|
343
|
+
rescue StandardError
|
344
|
+
false
|
345
|
+
end
|
346
|
+
end
|
347
|
+
|
348
|
+
def check_webpack_config_content
|
349
|
+
webpack_config_path = "config/webpack/webpack.config.js"
|
350
|
+
content = File.read(webpack_config_path)
|
351
|
+
|
352
|
+
if react_on_rails_config?(content)
|
353
|
+
add_success("✅ Webpack config includes React on Rails environment configuration")
|
354
|
+
add_info(" ℹ️ Environment-specific configs detected for optimal React on Rails integration")
|
355
|
+
elsif standard_shakapacker_config?(content)
|
356
|
+
add_warning(<<~MSG.strip)
|
357
|
+
⚠️ Standard Shakapacker webpack config detected.
|
358
|
+
|
359
|
+
React on Rails works better with environment-specific configuration.
|
360
|
+
Consider running: rails generate react_on_rails:install --force
|
361
|
+
This adds client and server environment configs for better performance.
|
362
|
+
MSG
|
363
|
+
else
|
364
|
+
add_info("ℹ️ Custom webpack config detected")
|
365
|
+
add_info(" 💡 Ensure config supports both client and server rendering")
|
366
|
+
add_info(" 💡 Verify React JSX transformation is configured")
|
367
|
+
add_info(" 💡 Check that asset output paths match Rails expectations")
|
368
|
+
end
|
369
|
+
end
|
370
|
+
|
371
|
+
private
|
372
|
+
|
373
|
+
def node_missing?
|
374
|
+
command = ReactOnRails::Utils.running_on_windows? ? "where" : "which"
|
375
|
+
_stdout, _stderr, status = Open3.capture3(command, "node")
|
376
|
+
!status.success?
|
377
|
+
end
|
378
|
+
|
379
|
+
def cli_exists?(command)
|
380
|
+
which_command = ReactOnRails::Utils.running_on_windows? ? "where" : "which"
|
381
|
+
_stdout, _stderr, status = Open3.capture3(which_command, command)
|
382
|
+
status.success?
|
383
|
+
end
|
384
|
+
|
385
|
+
def detect_used_package_manager
|
386
|
+
# Check for lock files to determine which package manager is being used
|
387
|
+
if File.exist?("yarn.lock")
|
388
|
+
"yarn"
|
389
|
+
elsif File.exist?("pnpm-lock.yaml")
|
390
|
+
"pnpm"
|
391
|
+
elsif File.exist?("bun.lockb")
|
392
|
+
"bun"
|
393
|
+
elsif File.exist?("package-lock.json")
|
394
|
+
"npm"
|
395
|
+
end
|
396
|
+
end
|
397
|
+
|
398
|
+
def get_package_manager_version(manager)
|
399
|
+
begin
|
400
|
+
stdout, _stderr, status = Open3.capture3(manager, "--version")
|
401
|
+
return stdout.strip if status.success? && !stdout.strip.empty?
|
402
|
+
rescue StandardError
|
403
|
+
# Ignore errors
|
404
|
+
end
|
405
|
+
"(version unknown)"
|
406
|
+
end
|
407
|
+
|
408
|
+
def get_deprecation_note(manager, version)
|
409
|
+
case manager
|
410
|
+
when "yarn"
|
411
|
+
" (Classic Yarn v1 - consider upgrading to Yarn Modern)" if /^1\./.match?(version)
|
412
|
+
end
|
413
|
+
end
|
414
|
+
|
415
|
+
def shakapacker_configured?
|
416
|
+
File.exist?("bin/shakapacker") &&
|
417
|
+
File.exist?("bin/shakapacker-dev-server") &&
|
418
|
+
File.exist?("config/shakapacker.yml") &&
|
419
|
+
File.exist?("config/webpack/webpack.config.js")
|
420
|
+
end
|
421
|
+
|
422
|
+
def shakapacker_in_gemfile?
|
423
|
+
gemfile = ENV["BUNDLE_GEMFILE"] || "Gemfile"
|
424
|
+
File.file?(gemfile) &&
|
425
|
+
File.foreach(gemfile).any? { |l| l.match?(/^\s*gem\s+['"]shakapacker['"]/) }
|
426
|
+
end
|
427
|
+
|
428
|
+
def react_on_rails_config?(content)
|
429
|
+
content.include?("envSpecificConfig") || content.include?("env.nodeEnv")
|
430
|
+
end
|
431
|
+
|
432
|
+
def standard_shakapacker_config?(content)
|
433
|
+
normalized = normalize_config_content(content)
|
434
|
+
shakapacker_patterns = [
|
435
|
+
/generateWebpackConfig.*require.*shakapacker/,
|
436
|
+
/webpackConfig.*require.*shakapacker/
|
437
|
+
]
|
438
|
+
shakapacker_patterns.any? { |pattern| normalized.match?(pattern) }
|
439
|
+
end
|
440
|
+
|
441
|
+
def normalize_config_content(content)
|
442
|
+
content.gsub(%r{//.*$}, "") # Remove single-line comments
|
443
|
+
.gsub(%r{/\*.*?\*/}m, "") # Remove multi-line comments
|
444
|
+
.gsub(/\s+/, " ") # Normalize whitespace
|
445
|
+
.strip
|
446
|
+
end
|
447
|
+
|
448
|
+
def required_react_dependencies
|
449
|
+
{
|
450
|
+
"react" => "React library",
|
451
|
+
"react-dom" => "React DOM library",
|
452
|
+
"@babel/preset-react" => "Babel React preset"
|
453
|
+
}
|
454
|
+
end
|
455
|
+
|
456
|
+
def additional_build_dependencies
|
457
|
+
{
|
458
|
+
"webpack" => "Webpack bundler",
|
459
|
+
"@babel/core" => "Babel compiler core",
|
460
|
+
"@babel/preset-env" => "Babel environment preset",
|
461
|
+
"css-loader" => "CSS loader for Webpack",
|
462
|
+
"style-loader" => "Style loader for Webpack",
|
463
|
+
"mini-css-extract-plugin" => "CSS extraction plugin",
|
464
|
+
"webpack-dev-server" => "Webpack development server"
|
465
|
+
}
|
466
|
+
end
|
467
|
+
|
468
|
+
# rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
469
|
+
def check_build_dependencies(package_json)
|
470
|
+
build_deps = additional_build_dependencies
|
471
|
+
all_deps = package_json["dependencies"]&.merge(package_json["devDependencies"] || {}) || {}
|
472
|
+
|
473
|
+
present_deps = []
|
474
|
+
missing_deps = []
|
475
|
+
|
476
|
+
build_deps.each do |package, description|
|
477
|
+
if all_deps[package]
|
478
|
+
present_deps << "#{description} (#{package})"
|
479
|
+
else
|
480
|
+
missing_deps << "#{description} (#{package})"
|
481
|
+
end
|
482
|
+
end
|
483
|
+
|
484
|
+
unless present_deps.empty?
|
485
|
+
short_list = present_deps.take(3).join(", ")
|
486
|
+
suffix = present_deps.length > 3 ? "..." : ""
|
487
|
+
add_info("✅ Build dependencies found: #{short_list}#{suffix}")
|
488
|
+
end
|
489
|
+
|
490
|
+
return if missing_deps.empty?
|
491
|
+
|
492
|
+
short_list = missing_deps.take(3).join(", ")
|
493
|
+
suffix = missing_deps.length > 3 ? "..." : ""
|
494
|
+
add_info("ℹ️ Optional build dependencies: #{short_list}#{suffix}")
|
495
|
+
end
|
496
|
+
# rubocop:enable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
497
|
+
|
498
|
+
def parse_package_json
|
499
|
+
JSON.parse(File.read("package.json"))
|
500
|
+
rescue JSON::ParserError
|
501
|
+
add_warning("⚠️ Could not parse package.json to check React dependencies")
|
502
|
+
nil
|
503
|
+
end
|
504
|
+
|
505
|
+
def find_missing_dependencies(package_json, required_deps)
|
506
|
+
all_deps = package_json["dependencies"]&.merge(package_json["devDependencies"] || {}) || {}
|
507
|
+
required_deps.keys.reject { |dep| all_deps[dep] }
|
508
|
+
end
|
509
|
+
|
510
|
+
def report_dependency_status(required_deps, missing_deps, package_json)
|
511
|
+
all_deps = package_json["dependencies"]&.merge(package_json["devDependencies"] || {}) || {}
|
512
|
+
|
513
|
+
required_deps.each do |dep, description|
|
514
|
+
add_success("✅ #{description} (#{dep}) is installed") if all_deps[dep]
|
515
|
+
end
|
516
|
+
|
517
|
+
return unless missing_deps.any?
|
518
|
+
|
519
|
+
add_warning(<<~MSG.strip)
|
520
|
+
⚠️ Missing React dependencies: #{missing_deps.join(', ')}
|
521
|
+
|
522
|
+
Install them with:
|
523
|
+
npm install #{missing_deps.join(' ')}
|
524
|
+
MSG
|
525
|
+
end
|
526
|
+
|
527
|
+
def check_version_patterns(npm_version, gem_version)
|
528
|
+
# Check for version range patterns in package.json
|
529
|
+
return unless /^[\^~]/.match?(npm_version)
|
530
|
+
|
531
|
+
pattern_type = npm_version[0] == "^" ? "caret (^)" : "tilde (~)"
|
532
|
+
add_warning(<<~MSG.strip)
|
533
|
+
⚠️ NPM package uses #{pattern_type} version pattern: #{npm_version}
|
534
|
+
|
535
|
+
While versions match, consider using exact version "#{gem_version}" in package.json
|
536
|
+
for guaranteed compatibility with the React on Rails gem.
|
537
|
+
MSG
|
538
|
+
end
|
539
|
+
|
540
|
+
# rubocop:disable Metrics/CyclomaticComplexity
|
541
|
+
def check_gemfile_version_patterns
|
542
|
+
gemfile_path = ENV["BUNDLE_GEMFILE"] || "Gemfile"
|
543
|
+
return unless File.exist?(gemfile_path)
|
544
|
+
|
545
|
+
begin
|
546
|
+
gemfile_content = File.read(gemfile_path)
|
547
|
+
react_on_rails_line = gemfile_content.lines.find { |line| line.match(/^\s*gem\s+['"]react_on_rails['"]/) }
|
548
|
+
|
549
|
+
return unless react_on_rails_line
|
550
|
+
|
551
|
+
# Check for version patterns in Gemfile
|
552
|
+
if /['"][~]/.match?(react_on_rails_line)
|
553
|
+
add_warning(<<~MSG.strip)
|
554
|
+
⚠️ Gemfile uses version pattern for react_on_rails gem.
|
555
|
+
|
556
|
+
Consider using exact version in Gemfile for guaranteed compatibility:
|
557
|
+
gem 'react_on_rails', '#{ReactOnRails::VERSION}'
|
558
|
+
MSG
|
559
|
+
elsif />=\s*/.match?(react_on_rails_line)
|
560
|
+
add_warning(<<~MSG.strip)
|
561
|
+
⚠️ Gemfile uses version range (>=) for react_on_rails gem.
|
562
|
+
|
563
|
+
Consider using exact version in Gemfile for guaranteed compatibility:
|
564
|
+
gem 'react_on_rails', '#{ReactOnRails::VERSION}'
|
565
|
+
MSG
|
566
|
+
end
|
567
|
+
rescue StandardError
|
568
|
+
# Ignore errors reading Gemfile
|
569
|
+
end
|
570
|
+
end
|
571
|
+
# rubocop:enable Metrics/CyclomaticComplexity
|
572
|
+
|
573
|
+
# rubocop:disable Metrics/CyclomaticComplexity
|
574
|
+
def report_dependency_versions(package_json)
|
575
|
+
all_deps = package_json["dependencies"]&.merge(package_json["devDependencies"] || {}) || {}
|
576
|
+
|
577
|
+
react_version = all_deps["react"]
|
578
|
+
react_dom_version = all_deps["react-dom"]
|
579
|
+
|
580
|
+
if react_version && react_dom_version
|
581
|
+
add_success("✅ React #{react_version}, React DOM #{react_dom_version}")
|
582
|
+
elsif react_version
|
583
|
+
add_success("✅ React #{react_version}")
|
584
|
+
add_warning("⚠️ React DOM not found")
|
585
|
+
elsif react_dom_version
|
586
|
+
add_warning("⚠️ React not found")
|
587
|
+
add_success("✅ React DOM #{react_dom_version}")
|
588
|
+
end
|
589
|
+
end
|
590
|
+
# rubocop:enable Metrics/CyclomaticComplexity
|
591
|
+
|
592
|
+
def report_shakapacker_version
|
593
|
+
return unless File.exist?("Gemfile.lock")
|
594
|
+
|
595
|
+
begin
|
596
|
+
lockfile_content = File.read("Gemfile.lock")
|
597
|
+
# Parse exact installed version from Gemfile.lock GEM section
|
598
|
+
shakapacker_match = lockfile_content.match(/^\s{4}shakapacker \(([^)>=<~]+)\)/)
|
599
|
+
if shakapacker_match
|
600
|
+
version = shakapacker_match[1].strip
|
601
|
+
add_info("📦 Shakapacker version: #{version}")
|
602
|
+
end
|
603
|
+
rescue StandardError
|
604
|
+
# Ignore errors in parsing Gemfile.lock
|
605
|
+
end
|
606
|
+
end
|
607
|
+
|
608
|
+
def report_shakapacker_version_with_threshold
|
609
|
+
return unless File.exist?("Gemfile.lock")
|
610
|
+
|
611
|
+
begin
|
612
|
+
lockfile_content = File.read("Gemfile.lock")
|
613
|
+
# Look for the exact installed version in the GEM section, not the dependency requirement
|
614
|
+
# This matches " shakapacker (8.0.0)" but not " shakapacker (>= 6.0)"
|
615
|
+
shakapacker_match = lockfile_content.match(/^\s{4}shakapacker \(([^)>=<~]+)\)/)
|
616
|
+
|
617
|
+
if shakapacker_match
|
618
|
+
version = shakapacker_match[1].strip
|
619
|
+
|
620
|
+
begin
|
621
|
+
# Use proper semantic version comparison
|
622
|
+
version_obj = Gem::Version.new(version)
|
623
|
+
threshold_version = Gem::Version.new("8.2")
|
624
|
+
|
625
|
+
if version_obj >= threshold_version
|
626
|
+
add_success("✅ Shakapacker #{version} (supports React on Rails auto-registration)")
|
627
|
+
else
|
628
|
+
add_warning("⚠️ Shakapacker #{version} - Version 8.2+ needed for React on Rails auto-registration")
|
629
|
+
end
|
630
|
+
rescue ArgumentError
|
631
|
+
# Fallback for invalid version strings
|
632
|
+
add_success("✅ Shakapacker #{version}")
|
633
|
+
end
|
634
|
+
else
|
635
|
+
add_success("✅ Shakapacker is configured")
|
636
|
+
end
|
637
|
+
rescue StandardError
|
638
|
+
add_success("✅ Shakapacker is configured")
|
639
|
+
end
|
640
|
+
end
|
641
|
+
|
642
|
+
def report_webpack_version
|
643
|
+
return unless File.exist?("package.json")
|
644
|
+
|
645
|
+
begin
|
646
|
+
package_json = JSON.parse(File.read("package.json"))
|
647
|
+
all_deps = package_json["dependencies"]&.merge(package_json["devDependencies"] || {}) || {}
|
648
|
+
|
649
|
+
webpack_version = all_deps["webpack"]
|
650
|
+
add_info("📦 Webpack version: #{webpack_version}") if webpack_version
|
651
|
+
rescue JSON::ParserError
|
652
|
+
# Handle JSON parsing errors
|
653
|
+
rescue StandardError
|
654
|
+
# Handle other file/access errors
|
655
|
+
end
|
656
|
+
end
|
657
|
+
end
|
658
|
+
# rubocop:enable Metrics/ClassLength
|
659
|
+
end
|