react_on_rails 16.4.0.rc.8 → 16.4.0.rc.10
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/Gemfile.lock +1 -1
- data/lib/generators/react_on_rails/base_generator.rb +369 -45
- data/lib/generators/react_on_rails/generator_helper.rb +17 -9
- data/lib/generators/react_on_rails/generator_messages.rb +20 -17
- data/lib/generators/react_on_rails/install_generator.rb +104 -26
- data/lib/generators/react_on_rails/js_dependency_manager.rb +24 -20
- data/lib/generators/react_on_rails/pro_generator.rb +2 -2
- data/lib/generators/react_on_rails/pro_setup.rb +224 -68
- data/lib/generators/react_on_rails/react_with_redux_generator.rb +7 -0
- data/lib/generators/react_on_rails/rsc_generator.rb +2 -2
- data/lib/generators/react_on_rails/rsc_setup.rb +257 -31
- data/lib/generators/react_on_rails/templates/base/base/app/controllers/hello_world_controller.rb +1 -1
- data/lib/generators/react_on_rails/templates/base/base/app/views/layouts/{hello_world.html.erb → react_on_rails_default.html.erb} +1 -1
- data/lib/generators/react_on_rails/templates/base/base/config/initializers/react_on_rails.rb.tt +14 -10
- data/lib/generators/react_on_rails/templates/base/base/config/shakapacker.yml.tt +9 -2
- data/lib/generators/react_on_rails/templates/dev_tests/spec/rails_helper.rb +9 -7
- data/lib/generators/react_on_rails/templates/dev_tests/spec/spec_helper.rb +2 -0
- data/lib/generators/react_on_rails/templates/rsc/base/app/controllers/{hello_server_controller.rb → hello_server_controller.rb.tt} +1 -1
- data/lib/generators/react_on_rails/templates/rsc/base/app/views/hello_server/index.html.erb +1 -2
- data/lib/react_on_rails/configuration.rb +4 -4
- data/lib/react_on_rails/dev/server_manager.rb +128 -3
- data/lib/react_on_rails/doctor.rb +744 -63
- data/lib/react_on_rails/helper.rb +52 -38
- data/lib/react_on_rails/locales/base.rb +2 -2
- data/lib/react_on_rails/locales/to_js.rb +2 -2
- data/lib/react_on_rails/packer_utils.rb +4 -4
- data/lib/react_on_rails/pro_helper.rb +1 -1
- data/lib/react_on_rails/server_rendering_pool/ruby_embedded_java_script.rb +1 -1
- data/lib/react_on_rails/system_checker.rb +1 -1
- data/lib/react_on_rails/test_helper/dev_assets_detector.rb +242 -0
- data/lib/react_on_rails/test_helper/ensure_assets_compiled.rb +22 -9
- data/lib/react_on_rails/test_helper/webpack_assets_compiler.rb +44 -14
- data/lib/react_on_rails/test_helper.rb +3 -2
- data/lib/react_on_rails/utils.rb +11 -12
- data/lib/react_on_rails/version.rb +1 -1
- data/lib/react_on_rails/version_checker.rb +39 -9
- data/lib/react_on_rails.rb +1 -0
- data/rakelib/shakapacker_examples.rake +6 -0
- data/rakelib/update_changelog.rake +169 -26
- metadata +5 -4
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: cdd0253666ff913084d12f9f5b3f24b41f0f33ed418165f54a5d038cb567012d
|
|
4
|
+
data.tar.gz: 510bcdc628f66dd6c9ddfae7be24582fef28ea35c263440e86a640b419bff379
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: fa0ec02748d25524f70fa7fe73803fc25c8c73249a17b953517ab3b226fa2101efc0443b3bab201737711f91d4a4f4d9d280e29005c28b505ae3d2d6d4bda153
|
|
7
|
+
data.tar.gz: 365c80cbf379d64e9c74958e96334fe494d2f9770f8fa0f1b01eae03f89e11fd08590459bbe3a021f6a4e3ff697a7ad78bcfa9fd93ac0646ada6007f6d471b8a
|
data/Gemfile.lock
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
require "rails/generators"
|
|
4
4
|
require "fileutils"
|
|
5
|
+
require "erb"
|
|
5
6
|
require_relative "generator_messages"
|
|
6
7
|
require_relative "generator_helper"
|
|
7
8
|
require_relative "js_dependency_manager"
|
|
@@ -47,6 +48,56 @@ module ReactOnRails
|
|
|
47
48
|
default: false,
|
|
48
49
|
hide: true
|
|
49
50
|
|
|
51
|
+
# Keep this map in sync with templates under:
|
|
52
|
+
# lib/generators/react_on_rails/templates/**/config/webpack/*.tt
|
|
53
|
+
MANAGED_WEBPACK_FILE_TEMPLATES = {
|
|
54
|
+
"clientWebpackConfig.js" => "base/base/config/webpack/clientWebpackConfig.js.tt",
|
|
55
|
+
"commonWebpackConfig.js" => "base/base/config/webpack/commonWebpackConfig.js.tt",
|
|
56
|
+
"test.js" => "base/base/config/webpack/test.js.tt",
|
|
57
|
+
"development.js" => "base/base/config/webpack/development.js.tt",
|
|
58
|
+
"production.js" => "base/base/config/webpack/production.js.tt",
|
|
59
|
+
"serverWebpackConfig.js" => "base/base/config/webpack/serverWebpackConfig.js.tt",
|
|
60
|
+
"rscWebpackConfig.js" => "rsc/base/config/webpack/rscWebpackConfig.js.tt",
|
|
61
|
+
"ServerClientOrBoth.js" => "base/base/config/webpack/ServerClientOrBoth.js.tt",
|
|
62
|
+
# Legacy filename generated by older React on Rails versions.
|
|
63
|
+
# We compare against the current options (including --pro/--rsc); if those
|
|
64
|
+
# don't match the original generation, cleanup intentionally preserves files.
|
|
65
|
+
"generateWebpackConfigs.js" => "base/base/config/webpack/ServerClientOrBoth.js.tt"
|
|
66
|
+
}.freeze
|
|
67
|
+
|
|
68
|
+
# Keep these helper delegates in sync with ERB calls in managed webpack
|
|
69
|
+
# templates. This is the intended template-facing API; missing delegates
|
|
70
|
+
# intentionally fail rendering and keep cleanup conservative by treating
|
|
71
|
+
# files as non-removable.
|
|
72
|
+
TemplateRenderContext = Struct.new(:generator, :config) do
|
|
73
|
+
def erb_binding
|
|
74
|
+
binding
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def add_documentation_reference(*args)
|
|
78
|
+
generator.__send__(:add_documentation_reference, *args)
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def use_pro?
|
|
82
|
+
generator.__send__(:use_pro?)
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def use_rsc?
|
|
86
|
+
generator.__send__(:use_rsc?)
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def shakapacker_version_9_or_higher?
|
|
90
|
+
generator.__send__(:shakapacker_version_9_or_higher?)
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
REMOVABLE_WEBPACK_FILES = (MANAGED_WEBPACK_FILE_TEMPLATES.keys +
|
|
95
|
+
%w[webpack.config.js]).freeze
|
|
96
|
+
DOCS_REFERENCE_MESSAGE = "// The source code including full typescript support is available at:"
|
|
97
|
+
TEMPLATE_RENDER_FAILED = Object.new.freeze # unique sentinel compared by identity via .equal?
|
|
98
|
+
private_constant :MANAGED_WEBPACK_FILE_TEMPLATES, :REMOVABLE_WEBPACK_FILES, :TemplateRenderContext,
|
|
99
|
+
:DOCS_REFERENCE_MESSAGE, :TEMPLATE_RENDER_FAILED
|
|
100
|
+
|
|
50
101
|
def add_hello_world_route
|
|
51
102
|
# RSC uses HelloServer instead of HelloWorld, but Redux still needs hello_world route
|
|
52
103
|
return if use_rsc? && !options.redux?
|
|
@@ -70,9 +121,9 @@ module ReactOnRails
|
|
|
70
121
|
.env.example
|
|
71
122
|
bin/shakapacker-precompile-hook]
|
|
72
123
|
|
|
73
|
-
#
|
|
74
|
-
# packs without requiring a hardcoded application.js pack entry.
|
|
75
|
-
base_files << "app/views/layouts/
|
|
124
|
+
# react_on_rails_default layout provides empty pack tags so React on Rails can
|
|
125
|
+
# inject generated packs without requiring a hardcoded application.js pack entry.
|
|
126
|
+
base_files << "app/views/layouts/react_on_rails_default.html.erb"
|
|
76
127
|
|
|
77
128
|
# HelloWorld controller only when not using RSC (RSC uses HelloServer)
|
|
78
129
|
# Exception: Redux still needs the HelloWorld controller even with RSC
|
|
@@ -104,7 +155,11 @@ module ReactOnRails
|
|
|
104
155
|
end
|
|
105
156
|
|
|
106
157
|
def copy_webpack_config
|
|
107
|
-
|
|
158
|
+
# Cleanup must run before writing new webpack/rspack configs so we only
|
|
159
|
+
# evaluate pre-existing stale entries, never files generated in this run.
|
|
160
|
+
cleanup_stale_webpack_config_dir_for_rspack
|
|
161
|
+
|
|
162
|
+
say "Adding #{using_rspack? ? 'Rspack' : 'Webpack'} config"
|
|
108
163
|
base_path = "base/base"
|
|
109
164
|
base_files = %w[babel.config.js
|
|
110
165
|
config/webpack/clientWebpackConfig.js
|
|
@@ -114,9 +169,7 @@ module ReactOnRails
|
|
|
114
169
|
config/webpack/production.js
|
|
115
170
|
config/webpack/serverWebpackConfig.js
|
|
116
171
|
config/webpack/ServerClientOrBoth.js]
|
|
117
|
-
config = {
|
|
118
|
-
message: "// The source code including full typescript support is available at:"
|
|
119
|
-
}
|
|
172
|
+
config = { message: DOCS_REFERENCE_MESSAGE }
|
|
120
173
|
base_files.each do |file|
|
|
121
174
|
template("#{base_path}/#{file}.tt", destination_config_path(file), config)
|
|
122
175
|
end
|
|
@@ -130,12 +183,12 @@ module ReactOnRails
|
|
|
130
183
|
config = "config/shakapacker.yml"
|
|
131
184
|
|
|
132
185
|
if options.shakapacker_just_installed?
|
|
133
|
-
|
|
186
|
+
say "Replacing Shakapacker default config with React on Rails version"
|
|
134
187
|
# Shakapacker's installer just created this file from scratch (no pre-existing config).
|
|
135
188
|
# Safe to overwrite silently with RoR's version-aware template (e.g., private_output_path).
|
|
136
189
|
template("#{base_path}#{config}.tt", config, force: true)
|
|
137
190
|
else
|
|
138
|
-
|
|
191
|
+
say "Adding Shakapacker #{ReactOnRails::PackerUtils.shakapacker_version} config"
|
|
139
192
|
# Thor handles the conflict: prompts user interactively, or respects --force/--skip flags.
|
|
140
193
|
template("#{base_path}#{config}.tt", config)
|
|
141
194
|
end
|
|
@@ -176,25 +229,42 @@ module ReactOnRails
|
|
|
176
229
|
end
|
|
177
230
|
|
|
178
231
|
def append_to_spec_rails_helper
|
|
179
|
-
|
|
180
|
-
if
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
add_configure_rspec_to_compile_assets(spec_helper) if File.exist?(spec_helper)
|
|
185
|
-
end
|
|
232
|
+
rspec_helper = preferred_rspec_helper_file
|
|
233
|
+
add_configure_rspec_to_compile_assets(rspec_helper) if rspec_helper
|
|
234
|
+
|
|
235
|
+
test_helper = File.join(destination_root, "test/test_helper.rb")
|
|
236
|
+
add_configure_minitest_to_compile_assets(test_helper) if File.exist?(test_helper)
|
|
186
237
|
end
|
|
187
238
|
|
|
188
|
-
CONFIGURE_RSPEC_TO_COMPILE_ASSETS =
|
|
239
|
+
CONFIGURE_RSPEC_TO_COMPILE_ASSETS = <<~STR
|
|
189
240
|
RSpec.configure do |config|
|
|
190
241
|
# Ensure that if we are running js tests, we are using latest webpack assets
|
|
191
242
|
# This will use the defaults of :js and :server_rendering meta tags
|
|
243
|
+
# Requires config.build_test_command in config/initializers/react_on_rails.rb.
|
|
244
|
+
# This is the default setup for React on Rails generated apps.
|
|
192
245
|
ReactOnRails::TestHelper.configure_rspec_to_compile_assets(config)
|
|
193
246
|
end
|
|
194
247
|
STR
|
|
195
248
|
|
|
249
|
+
CONFIGURE_MINITEST_TO_COMPILE_ASSETS = <<~STR
|
|
250
|
+
# Ensure that tests run against fresh webpack assets.
|
|
251
|
+
ActiveSupport::TestCase.setup do
|
|
252
|
+
ReactOnRails::TestHelper.ensure_assets_compiled
|
|
253
|
+
end
|
|
254
|
+
STR
|
|
255
|
+
|
|
196
256
|
private
|
|
197
257
|
|
|
258
|
+
def preferred_rspec_helper_file
|
|
259
|
+
rails_helper = File.join(destination_root, "spec/rails_helper.rb")
|
|
260
|
+
return rails_helper if File.exist?(rails_helper)
|
|
261
|
+
|
|
262
|
+
spec_helper = File.join(destination_root, "spec/spec_helper.rb")
|
|
263
|
+
return spec_helper if File.exist?(spec_helper)
|
|
264
|
+
|
|
265
|
+
nil
|
|
266
|
+
end
|
|
267
|
+
|
|
198
268
|
def copy_webpack_main_config(base_path, config)
|
|
199
269
|
webpack_config_path = bundler_main_config_path
|
|
200
270
|
|
|
@@ -202,16 +272,16 @@ module ReactOnRails
|
|
|
202
272
|
existing_content = File.read(webpack_config_path)
|
|
203
273
|
|
|
204
274
|
# Check if it's the standard Shakapacker config that we can safely replace
|
|
205
|
-
if standard_shakapacker_config?(existing_content)
|
|
275
|
+
if standard_shakapacker_config?(existing_content, strip_comments: true)
|
|
206
276
|
# Remove the file first to avoid conflict prompt, then recreate it
|
|
207
277
|
remove_file(webpack_config_path, verbose: false)
|
|
208
278
|
# Show what we're doing
|
|
209
|
-
|
|
210
|
-
|
|
279
|
+
say_status :replace,
|
|
280
|
+
"#{webpack_config_path} (auto-upgrading from standard Shakapacker to React on Rails config)",
|
|
281
|
+
:green
|
|
211
282
|
template("#{base_path}/config/webpack/webpack.config.js.tt", webpack_config_path, config)
|
|
212
283
|
elsif react_on_rails_config?(existing_content)
|
|
213
|
-
|
|
214
|
-
"(already React on Rails compatible)"
|
|
284
|
+
say_status :identical, "#{webpack_config_path} (already React on Rails compatible)", :blue
|
|
215
285
|
# Skip - don't need to do anything
|
|
216
286
|
else
|
|
217
287
|
handle_custom_webpack_config(base_path, config, webpack_config_path)
|
|
@@ -226,23 +296,25 @@ module ReactOnRails
|
|
|
226
296
|
# Custom config - ask user
|
|
227
297
|
config_file_name = File.basename(webpack_config_path)
|
|
228
298
|
bundler_name = using_rspack? ? "rspack" : "webpack"
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
299
|
+
say ""
|
|
300
|
+
say_status :notice, "Your #{config_file_name} appears to be customized.", :yellow
|
|
301
|
+
say "React on Rails needs to replace it with an environment-specific loader."
|
|
302
|
+
say "Your current config will be backed up to #{config_file_name}.backup"
|
|
232
303
|
|
|
233
304
|
if yes?("Replace #{config_file_name} with React on Rails version? (Y/n)")
|
|
234
305
|
# Create backup
|
|
235
306
|
backup_path = "#{webpack_config_path}.backup"
|
|
236
307
|
if File.exist?(webpack_config_path)
|
|
237
308
|
FileUtils.cp(webpack_config_path, backup_path)
|
|
238
|
-
|
|
309
|
+
say_status :create, "#{backup_path} (backup of your custom config)", :green
|
|
239
310
|
end
|
|
240
311
|
|
|
241
312
|
template("#{base_path}/config/webpack/webpack.config.js.tt", webpack_config_path, config)
|
|
242
313
|
else
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
314
|
+
say_status :skip, webpack_config_path, :yellow
|
|
315
|
+
say_status :warning,
|
|
316
|
+
"React on Rails may not work correctly without the environment-specific #{bundler_name} config",
|
|
317
|
+
:red
|
|
246
318
|
end
|
|
247
319
|
end
|
|
248
320
|
|
|
@@ -254,25 +326,265 @@ module ReactOnRails
|
|
|
254
326
|
end
|
|
255
327
|
end
|
|
256
328
|
|
|
257
|
-
def
|
|
329
|
+
def cleanup_stale_webpack_config_dir_for_rspack
|
|
330
|
+
return unless cleanup_stale_webpack_config_dir?
|
|
331
|
+
|
|
332
|
+
webpack_config_relative_dir = "config/webpack"
|
|
333
|
+
all_entries = stale_webpack_config_entries
|
|
334
|
+
unless all_entries
|
|
335
|
+
say_status :warning,
|
|
336
|
+
"Keeping #{webpack_config_relative_dir}; could not read directory entries " \
|
|
337
|
+
"(permission denied or path changed)",
|
|
338
|
+
:yellow
|
|
339
|
+
return
|
|
340
|
+
end
|
|
341
|
+
if all_entries.empty?
|
|
342
|
+
say_status :skip, "#{webpack_config_relative_dir} (empty directory, leaving as-is)", :yellow
|
|
343
|
+
return
|
|
344
|
+
end
|
|
345
|
+
|
|
346
|
+
return warn_dotfiles_only_webpack_dir(webpack_config_relative_dir, all_entries) if all_dotfiles?(all_entries)
|
|
347
|
+
|
|
348
|
+
# We only clean up known top-level files. Any directory or unknown entry is
|
|
349
|
+
# treated as non-removable so we don't recurse into user-managed content.
|
|
350
|
+
non_removable_entries = non_removable_webpack_entries(all_entries)
|
|
351
|
+
removable_entries = removable_webpack_entries(all_entries)
|
|
352
|
+
if non_removable_entries.empty?
|
|
353
|
+
remove_stale_webpack_dir(webpack_config_relative_dir)
|
|
354
|
+
return
|
|
355
|
+
end
|
|
356
|
+
|
|
357
|
+
if all_dotfiles?(non_removable_entries)
|
|
358
|
+
handle_dotfile_only_non_removable_entries(
|
|
359
|
+
webpack_config_relative_dir,
|
|
360
|
+
removable_entries,
|
|
361
|
+
non_removable_entries
|
|
362
|
+
)
|
|
363
|
+
return
|
|
364
|
+
end
|
|
365
|
+
|
|
366
|
+
removed_entries = remove_stale_webpack_files(webpack_config_relative_dir, removable_entries)
|
|
367
|
+
warn_non_removable_webpack_entries(webpack_config_relative_dir, non_removable_entries, removed_entries)
|
|
368
|
+
end
|
|
369
|
+
|
|
370
|
+
def remove_stale_webpack_dir(webpack_config_relative_dir)
|
|
371
|
+
# Thor's remove_dir uses paths relative to destination_root.
|
|
372
|
+
# TOCTOU: a file created between the removability check and remove_dir
|
|
373
|
+
# would be deleted. Acceptable for this generator cleanup path.
|
|
374
|
+
remove_dir(webpack_config_relative_dir, verbose: false)
|
|
375
|
+
say_status :remove,
|
|
376
|
+
"#{webpack_config_relative_dir} (stale webpack configs after switching to --rspack)",
|
|
377
|
+
:green
|
|
378
|
+
end
|
|
379
|
+
|
|
380
|
+
def handle_dotfile_only_non_removable_entries(
|
|
381
|
+
webpack_config_relative_dir,
|
|
382
|
+
removable_entries,
|
|
383
|
+
non_removable_entries
|
|
384
|
+
)
|
|
385
|
+
removed_entries = remove_stale_webpack_files(webpack_config_relative_dir, removable_entries)
|
|
386
|
+
warn_dotfiles_in_webpack_dir(webpack_config_relative_dir, non_removable_entries, removed_entries)
|
|
387
|
+
end
|
|
388
|
+
|
|
389
|
+
def cleanup_stale_webpack_config_dir?
|
|
390
|
+
using_rspack? && Dir.exist?(stale_webpack_config_dir)
|
|
391
|
+
end
|
|
392
|
+
|
|
393
|
+
def all_dotfiles?(entries)
|
|
394
|
+
entries.any? && entries.all? { |entry| entry.start_with?(".") }
|
|
395
|
+
end
|
|
396
|
+
|
|
397
|
+
def warn_dotfiles_only_webpack_dir(webpack_config_relative_dir, all_entries)
|
|
398
|
+
say_status :warning,
|
|
399
|
+
"Keeping #{webpack_config_relative_dir}; only dotfiles found: " \
|
|
400
|
+
"#{all_entries.join(', ')}",
|
|
401
|
+
:yellow
|
|
402
|
+
end
|
|
403
|
+
|
|
404
|
+
def warn_non_removable_webpack_entries(webpack_config_relative_dir, non_removable_entries, removed_entries = [])
|
|
405
|
+
non_dotfile_entries = non_removable_entries.reject { |entry| entry.start_with?(".") }
|
|
406
|
+
dotfiles = non_removable_entries.select { |entry| entry.start_with?(".") }
|
|
407
|
+
non_removable_directories, non_removable_files = non_dotfile_entries.partition do |entry|
|
|
408
|
+
directory_entry?(entry)
|
|
409
|
+
end
|
|
410
|
+
|
|
411
|
+
non_removable_sections = []
|
|
412
|
+
non_removable_sections << "files: #{non_removable_files.join(', ')}" if non_removable_files.any?
|
|
413
|
+
if non_removable_directories.any?
|
|
414
|
+
non_removable_sections << "directories: #{non_removable_directories.join(', ')}"
|
|
415
|
+
end
|
|
416
|
+
non_removable_sections << "dotfiles: #{dotfiles.join(', ')}" if dotfiles.any?
|
|
417
|
+
|
|
418
|
+
non_removable_sections_message = non_removable_sections.join("; ")
|
|
419
|
+
removed_entries_message = if removed_entries.any?
|
|
420
|
+
" Removed stale managed files: #{removed_entries.join(', ')}."
|
|
421
|
+
else
|
|
422
|
+
""
|
|
423
|
+
end
|
|
424
|
+
say_status :warning,
|
|
425
|
+
"Keeping #{webpack_config_relative_dir}; " \
|
|
426
|
+
"custom/non-removable entries detected: #{non_removable_sections_message}." \
|
|
427
|
+
"#{removed_entries_message}",
|
|
428
|
+
:yellow
|
|
429
|
+
end
|
|
430
|
+
|
|
431
|
+
def directory_entry?(entry)
|
|
432
|
+
File.directory?(File.join(stale_webpack_config_dir, entry))
|
|
433
|
+
rescue Errno::EACCES, Errno::ELOOP, Errno::ENOENT
|
|
434
|
+
false
|
|
435
|
+
end
|
|
436
|
+
|
|
437
|
+
def warn_dotfiles_in_webpack_dir(webpack_config_relative_dir, dotfiles, removed_entries)
|
|
438
|
+
removed_entries_message = if removed_entries.any?
|
|
439
|
+
" Removed stale managed files: #{removed_entries.join(', ')}."
|
|
440
|
+
else
|
|
441
|
+
""
|
|
442
|
+
end
|
|
443
|
+
say_status :warning,
|
|
444
|
+
"Keeping #{webpack_config_relative_dir}; dotfiles present: #{dotfiles.join(', ')}. " \
|
|
445
|
+
"Remove them manually if you want the directory cleaned up." \
|
|
446
|
+
"#{removed_entries_message}",
|
|
447
|
+
:yellow
|
|
448
|
+
end
|
|
449
|
+
|
|
450
|
+
def non_removable_webpack_entries(all_entries)
|
|
451
|
+
all_entries.select do |entry|
|
|
452
|
+
entry.start_with?(".") || !removable_webpack_entry?(entry)
|
|
453
|
+
end
|
|
454
|
+
end
|
|
455
|
+
|
|
456
|
+
def removable_webpack_entries(all_entries)
|
|
457
|
+
all_entries.select { |entry| removable_webpack_entry?(entry) }
|
|
458
|
+
end
|
|
459
|
+
|
|
460
|
+
def remove_stale_webpack_files(webpack_config_relative_dir, entries)
|
|
461
|
+
entries.each_with_object([]) do |entry, removed_entries|
|
|
462
|
+
relative_path = File.join(webpack_config_relative_dir, entry)
|
|
463
|
+
full_path = File.join(destination_root, relative_path)
|
|
464
|
+
remove_file(relative_path, verbose: false)
|
|
465
|
+
removed_entries << entry unless File.exist?(full_path)
|
|
466
|
+
rescue Errno::EACCES, Errno::ELOOP, Errno::ENOENT, Errno::ENOTDIR
|
|
467
|
+
# If we cannot stat after removal attempt, conservatively report as not removed.
|
|
468
|
+
nil
|
|
469
|
+
end
|
|
470
|
+
end
|
|
471
|
+
|
|
472
|
+
def removable_webpack_entry?(entry)
|
|
473
|
+
return false unless REMOVABLE_WEBPACK_FILES.include?(entry)
|
|
474
|
+
|
|
475
|
+
full_path = File.join(stale_webpack_config_dir, entry)
|
|
476
|
+
return false if File.symlink?(full_path)
|
|
477
|
+
return false unless File.file?(full_path)
|
|
478
|
+
|
|
479
|
+
content = safe_read_cleanup_file(full_path)
|
|
480
|
+
return false unless content
|
|
481
|
+
|
|
482
|
+
return removable_webpack_main_config?(content) if entry == "webpack.config.js"
|
|
483
|
+
|
|
484
|
+
removable_managed_webpack_entry?(entry, content)
|
|
485
|
+
rescue Errno::EACCES, Errno::ELOOP, Errno::ENOENT, Errno::ENOTDIR
|
|
486
|
+
false
|
|
487
|
+
end
|
|
488
|
+
|
|
489
|
+
def removable_webpack_main_config?(content)
|
|
490
|
+
webpack_template = rendered_template_for_cleanup("base/base/config/webpack/webpack.config.js.tt")
|
|
491
|
+
return standard_shakapacker_config?(content) if webpack_template.equal?(TEMPLATE_RENDER_FAILED)
|
|
492
|
+
|
|
493
|
+
# Cleanup is deliberately comment-sensitive (unlike copy_webpack_main_config)
|
|
494
|
+
# so comment-only edits keep the file as a potential customization.
|
|
495
|
+
standard_shakapacker_config?(content) || content_matches_template?(content, webpack_template)
|
|
496
|
+
end
|
|
497
|
+
|
|
498
|
+
def removable_managed_webpack_entry?(entry, content)
|
|
499
|
+
template_path = MANAGED_WEBPACK_FILE_TEMPLATES[entry]
|
|
500
|
+
return false unless template_path
|
|
501
|
+
|
|
502
|
+
rendered_template = rendered_template_for_cleanup(template_path)
|
|
503
|
+
return false if rendered_template.equal?(TEMPLATE_RENDER_FAILED)
|
|
504
|
+
|
|
505
|
+
content_matches_template?(content, rendered_template)
|
|
506
|
+
end
|
|
507
|
+
|
|
508
|
+
def rendered_template_for_cleanup(template_path)
|
|
509
|
+
@rendered_template_cache ||= {}
|
|
510
|
+
@rendered_template_cache[template_path] ||= begin
|
|
511
|
+
# Cleanup comparisons only need the injected documentation comment.
|
|
512
|
+
template_doc_config = { message: DOCS_REFERENCE_MESSAGE }
|
|
513
|
+
template_content = File.read(File.join(self.class.source_root, template_path))
|
|
514
|
+
# Render against current generator options. Any mismatch is treated as non-removable,
|
|
515
|
+
# which is intentional because cleanup should be conservative.
|
|
516
|
+
# Note: files originally generated with --pro or --rsc will not match when the
|
|
517
|
+
# current run omits those options; in that case, we preserve the directory.
|
|
518
|
+
# Templates rely on config[:message] plus a small helper subset exposed by
|
|
519
|
+
# TemplateRenderContext (add_documentation_reference, use_pro?, use_rsc?,
|
|
520
|
+
# shakapacker_version_9_or_higher?). Missing method delegates raise
|
|
521
|
+
# NoMethodError and are caught below, treating the file as non-removable.
|
|
522
|
+
# Missing config hash keys return nil silently, so any new config key
|
|
523
|
+
# required by templates must be added to template_doc_config above.
|
|
524
|
+
# Use TemplateRenderContext#erb_binding to avoid leaking method-local
|
|
525
|
+
# variables from rendered_template_for_cleanup into the ERB scope.
|
|
526
|
+
template_render_context = TemplateRenderContext.new(self, template_doc_config)
|
|
527
|
+
ERB.new(template_content, trim_mode: "-").result(template_render_context.erb_binding)
|
|
528
|
+
rescue StandardError => e
|
|
529
|
+
say_status :warning,
|
|
530
|
+
"Could not render template #{template_path} for cleanup check (#{e.class}: #{e.message}); " \
|
|
531
|
+
"treating as non-removable",
|
|
532
|
+
:yellow
|
|
533
|
+
# Rendering failures should never abort installation. Returning a sentinel
|
|
534
|
+
# guarantees a non-match so the file is treated as non-removable.
|
|
535
|
+
# We cache the sentinel intentionally (same options within one generator run)
|
|
536
|
+
# so repeated checks stay consistent and avoid noisy duplicate warnings.
|
|
537
|
+
TEMPLATE_RENDER_FAILED
|
|
538
|
+
end
|
|
539
|
+
end
|
|
540
|
+
|
|
541
|
+
def stale_webpack_config_dir
|
|
542
|
+
File.join(destination_root, "config/webpack")
|
|
543
|
+
end
|
|
544
|
+
|
|
545
|
+
def stale_webpack_config_entries
|
|
546
|
+
Dir.children(stale_webpack_config_dir).sort
|
|
547
|
+
rescue Errno::EACCES, Errno::ENOENT, Errno::ENOTDIR
|
|
548
|
+
# TOCTOU: directory may disappear between exist? check and read
|
|
549
|
+
nil
|
|
550
|
+
end
|
|
551
|
+
|
|
552
|
+
def safe_read_cleanup_file(path)
|
|
553
|
+
File.read(path)
|
|
554
|
+
rescue Errno::EACCES, Errno::ELOOP, Errno::ENOENT, Errno::EISDIR, Errno::ENOTDIR
|
|
555
|
+
nil
|
|
556
|
+
end
|
|
557
|
+
|
|
558
|
+
def standard_shakapacker_config?(content, strip_comments: false)
|
|
559
|
+
# Keep strip_comments false by default so comment-only edits are treated as
|
|
560
|
+
# potential customizations during cleanup. Callers can pass true when they
|
|
561
|
+
# need historical comment-insensitive replacement matching.
|
|
258
562
|
# Get the expected default config based on Shakapacker version
|
|
259
563
|
expected_configs = shakapacker_default_configs
|
|
260
564
|
|
|
261
565
|
# Check if the content matches any of the known default configurations
|
|
262
|
-
expected_configs.any? { |config| content_matches_template?(content, config) }
|
|
566
|
+
expected_configs.any? { |config| content_matches_template?(content, config, strip_comments: strip_comments) }
|
|
263
567
|
end
|
|
264
568
|
|
|
265
|
-
def content_matches_template?(content, template)
|
|
569
|
+
def content_matches_template?(content, template, strip_comments: false)
|
|
266
570
|
# Normalize whitespace and compare
|
|
267
|
-
normalize_config_content(content) ==
|
|
571
|
+
normalize_config_content(content, strip_comments: strip_comments) ==
|
|
572
|
+
normalize_config_content(template, strip_comments: strip_comments)
|
|
268
573
|
end
|
|
269
574
|
|
|
270
|
-
def normalize_config_content(content)
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
575
|
+
def normalize_config_content(content, strip_comments: false)
|
|
576
|
+
normalized_content = content
|
|
577
|
+
if strip_comments
|
|
578
|
+
# Replacement detection historically removed inline // comments too.
|
|
579
|
+
# Note: this also strips `//` in string literals (for example URLs),
|
|
580
|
+
# which can yield false-negatives and prompt for replacement.
|
|
581
|
+
normalized_content = normalized_content.gsub(%r{//.*$}, "")
|
|
582
|
+
.gsub(%r{/\*.*?\*/}m, "")
|
|
583
|
+
end
|
|
584
|
+
|
|
585
|
+
# Normalize whitespace while preserving comments by default so added comments
|
|
586
|
+
# count as potential customizations and keep cleanup conservative.
|
|
587
|
+
normalized_content.gsub(/\s+/, " ").strip
|
|
276
588
|
end
|
|
277
589
|
|
|
278
590
|
def shakapacker_default_configs
|
|
@@ -359,15 +671,27 @@ module ReactOnRails
|
|
|
359
671
|
end
|
|
360
672
|
|
|
361
673
|
def add_configure_rspec_to_compile_assets(helper_file)
|
|
362
|
-
|
|
363
|
-
|
|
674
|
+
content = File.read(helper_file)
|
|
675
|
+
return if content.match?(/^\s*[^#\s][^#]*ReactOnRails::TestHelper\.configure_rspec_to_compile_assets/)
|
|
676
|
+
|
|
677
|
+
updated_content = content.sub("RSpec.configure do |config|", CONFIGURE_RSPEC_TO_COMPILE_ASSETS)
|
|
678
|
+
return if updated_content == content
|
|
679
|
+
|
|
680
|
+
File.write(helper_file, updated_content)
|
|
681
|
+
end
|
|
682
|
+
|
|
683
|
+
def add_configure_minitest_to_compile_assets(helper_file)
|
|
684
|
+
content = File.read(helper_file)
|
|
685
|
+
return if content.match?(/^\s*[^#\s][^#]*ReactOnRails::TestHelper\.ensure_assets_compiled/)
|
|
686
|
+
|
|
687
|
+
append_to_file(helper_file, "\n\n#{CONFIGURE_MINITEST_TO_COMPILE_ASSETS}\n")
|
|
364
688
|
end
|
|
365
689
|
|
|
366
690
|
def configure_rspack_in_shakapacker
|
|
367
691
|
shakapacker_config_path = "config/shakapacker.yml"
|
|
368
692
|
return unless File.exist?(shakapacker_config_path)
|
|
369
693
|
|
|
370
|
-
|
|
694
|
+
say "🔧 Configuring Shakapacker for Rspack...", :yellow
|
|
371
695
|
|
|
372
696
|
# Use regex replacement to preserve file structure (comments, anchors, aliases)
|
|
373
697
|
# This replaces ALL occurrences of assets_bundler, not just in default section
|
|
@@ -385,7 +709,7 @@ module ReactOnRails
|
|
|
385
709
|
'\1swc\2'
|
|
386
710
|
)
|
|
387
711
|
|
|
388
|
-
|
|
712
|
+
say "✅ Updated shakapacker.yml for Rspack", :green
|
|
389
713
|
end
|
|
390
714
|
|
|
391
715
|
def configure_precompile_hook_in_shakapacker
|
|
@@ -406,7 +730,7 @@ module ReactOnRails
|
|
|
406
730
|
/^(\s*)#\s*precompile_hook:\s*~\s*$/,
|
|
407
731
|
"\\1precompile_hook: 'bin/shakapacker-precompile-hook'"
|
|
408
732
|
|
|
409
|
-
|
|
733
|
+
say "✅ Configured precompile_hook in shakapacker.yml", :green
|
|
410
734
|
end
|
|
411
735
|
|
|
412
736
|
def configure_private_output_path_in_shakapacker
|
|
@@ -446,7 +770,7 @@ module ReactOnRails
|
|
|
446
770
|
|
|
447
771
|
return unless File.read(shakapacker_config_path).match?(/^\s+private_output_path:\s*ssr-generated/)
|
|
448
772
|
|
|
449
|
-
|
|
773
|
+
say "✅ Configured private_output_path in shakapacker.yml", :green
|
|
450
774
|
end
|
|
451
775
|
end
|
|
452
776
|
end
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require "rainbow"
|
|
4
3
|
require "json"
|
|
5
4
|
|
|
5
|
+
# rubocop:disable Metrics/ModuleLength
|
|
6
6
|
module GeneratorHelper
|
|
7
7
|
def package_json
|
|
8
8
|
# Lazy load package_json gem only when actually needed for dependency management
|
|
@@ -10,12 +10,13 @@ module GeneratorHelper
|
|
|
10
10
|
require "package_json" unless defined?(PackageJson)
|
|
11
11
|
@package_json ||= PackageJson.read
|
|
12
12
|
rescue LoadError
|
|
13
|
-
|
|
14
|
-
|
|
13
|
+
say_status :warning, "package_json gem not available. This is expected before Shakapacker installation.", :yellow
|
|
14
|
+
say_status :warning, "Dependencies will be installed using the default package manager after Shakapacker setup.",
|
|
15
|
+
:yellow
|
|
15
16
|
nil
|
|
16
17
|
rescue StandardError => e
|
|
17
|
-
|
|
18
|
-
|
|
18
|
+
say_status :warning, "Could not read package.json: #{e.message}", :yellow
|
|
19
|
+
say_status :warning, "This is normal before Shakapacker creates the package.json file.", :yellow
|
|
19
20
|
nil
|
|
20
21
|
end
|
|
21
22
|
|
|
@@ -32,8 +33,8 @@ module GeneratorHelper
|
|
|
32
33
|
end
|
|
33
34
|
true
|
|
34
35
|
rescue StandardError => e
|
|
35
|
-
|
|
36
|
-
|
|
36
|
+
say_status :warning, "Could not add packages via package_json gem: #{e.message}", :yellow
|
|
37
|
+
say_status :warning, "Will fall back to direct npm commands.", :yellow
|
|
37
38
|
false
|
|
38
39
|
end
|
|
39
40
|
end
|
|
@@ -93,9 +94,11 @@ module GeneratorHelper
|
|
|
93
94
|
end
|
|
94
95
|
|
|
95
96
|
def print_generator_messages
|
|
97
|
+
# GeneratorMessages stores pre-colored strings, so we strip ANSI manually for --no-color output.
|
|
98
|
+
no_color = !shell.is_a?(Thor::Shell::Color)
|
|
96
99
|
GeneratorMessages.messages.each do |message|
|
|
97
|
-
|
|
98
|
-
|
|
100
|
+
say(no_color ? message.to_s.gsub(/\e\[[0-9;]*m/, "") : message)
|
|
101
|
+
say "" # Blank line after each message for readability
|
|
99
102
|
end
|
|
100
103
|
end
|
|
101
104
|
|
|
@@ -129,6 +132,10 @@ module GeneratorHelper
|
|
|
129
132
|
@pro_gem_installed = Gem.loaded_specs.key?("react_on_rails_pro") || gem_in_lockfile?("react_on_rails_pro")
|
|
130
133
|
end
|
|
131
134
|
|
|
135
|
+
def mark_pro_gem_installed!
|
|
136
|
+
@pro_gem_installed = true
|
|
137
|
+
end
|
|
138
|
+
|
|
132
139
|
# Check if Pro features should be enabled
|
|
133
140
|
# Returns true if --pro flag is set OR --rsc flag is set (RSC implies Pro)
|
|
134
141
|
#
|
|
@@ -347,3 +354,4 @@ module GeneratorHelper
|
|
|
347
354
|
config.dig("default", "assets_bundler") == "rspack"
|
|
348
355
|
end
|
|
349
356
|
end
|
|
357
|
+
# rubocop:enable Metrics/ModuleLength
|