react_on_rails 17.0.0.rc.1 → 17.0.0.rc.3
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/.rubocop.yml +1 -0
- data/Gemfile.lock +1 -33
- data/Rakefile +1 -1
- data/lib/generators/USAGE +6 -0
- data/lib/generators/react_on_rails/base_generator.rb +39 -11
- data/lib/generators/react_on_rails/demo_page_config.rb +6 -6
- data/lib/generators/react_on_rails/generator_helper.rb +46 -1
- data/lib/generators/react_on_rails/generator_messages/ci_section.rb +2 -2
- data/lib/generators/react_on_rails/generator_messages/package_manager_detection.rb +9 -9
- data/lib/generators/react_on_rails/generator_messages/shakapacker_status_section.rb +1 -1
- data/lib/generators/react_on_rails/generator_messages.rb +7 -7
- data/lib/generators/react_on_rails/install_generator.rb +66 -9
- data/lib/generators/react_on_rails/js_dependency_manager.rb +197 -64
- data/lib/generators/react_on_rails/pro_generator.rb +13 -13
- data/lib/generators/react_on_rails/react_no_redux_generator.rb +20 -8
- data/lib/generators/react_on_rails/react_with_redux_generator.rb +29 -5
- data/lib/generators/react_on_rails/rsc_generator.rb +5 -0
- data/lib/generators/react_on_rails/rsc_setup/client_references.rb +514 -89
- data/lib/generators/react_on_rails/rsc_setup.rb +82 -29
- data/lib/generators/react_on_rails/shakapacker_precompile_hook_helper.rb +338 -22
- data/lib/generators/react_on_rails/templates/agent_files/.cursor/rules/react-on-rails.mdc +11 -0
- data/lib/generators/react_on_rails/templates/agent_files/.github/copilot-instructions.md +7 -0
- data/lib/generators/react_on_rails/templates/agent_files/AGENTS.md +164 -0
- data/lib/generators/react_on_rails/templates/agent_files/CLAUDE.md +8 -0
- data/lib/generators/react_on_rails/templates/base/base/bin/shakapacker-precompile-hook +88 -0
- data/lib/generators/react_on_rails/templates/base/base/config/webpack/clientWebpackConfig.js.tt +2 -2
- data/lib/generators/react_on_rails/templates/base/base/config/webpack/commonWebpackConfig.js.tt +81 -1
- data/lib/generators/react_on_rails/templates/base/base/config/webpack/serverWebpackConfig.js.tt +5 -5
- data/lib/generators/react_on_rails/templates/base/tailwind/app/javascript/src/HelloWorld/ror_components/HelloWorld.client.jsx +30 -0
- data/lib/generators/react_on_rails/templates/base/tailwind/app/javascript/src/HelloWorld/ror_components/HelloWorld.client.tsx +34 -0
- data/lib/generators/react_on_rails/templates/base/tailwind/app/javascript/stylesheets/application.css +1 -0
- data/lib/generators/react_on_rails/templates/dev_tests/spec/rails_helper.rb +1 -1
- data/lib/generators/react_on_rails/templates/redux/tailwind/app/javascript/bundles/HelloWorld/components/HelloWorld.jsx +25 -0
- data/lib/generators/react_on_rails/templates/redux/tailwind/app/javascript/bundles/HelloWorld/components/HelloWorld.tsx +29 -0
- data/lib/generators/react_on_rails/templates/rsc/base/config/webpack/rscWebpackConfig.js.tt +133 -13
- data/lib/react_on_rails/controller.rb +2 -2
- data/lib/react_on_rails/dev/pack_generator.rb +5 -5
- data/lib/react_on_rails/dev/port_selector.rb +4 -4
- data/lib/react_on_rails/dev/process_manager.rb +3 -3
- data/lib/react_on_rails/dev/server_manager.rb +48 -148
- data/lib/react_on_rails/dev/service_checker.rb +1 -1
- data/lib/react_on_rails/doctor.rb +200 -128
- data/lib/react_on_rails/error.rb +3 -0
- data/lib/react_on_rails/font_helper.rb +162 -0
- data/lib/react_on_rails/git_utils.rb +3 -3
- data/lib/react_on_rails/helper.rb +183 -32
- data/lib/react_on_rails/locales/base.rb +2 -2
- data/lib/react_on_rails/packer_utils.rb +3 -3
- data/lib/react_on_rails/packs_generator.rb +309 -56
- data/lib/react_on_rails/prerender_error.rb +5 -5
- data/lib/react_on_rails/pro_helper.rb +2 -2
- data/lib/react_on_rails/pro_migration.rb +2 -2
- data/lib/react_on_rails/react_component/render_options.rb +1 -1
- data/lib/react_on_rails/server_rendering_pool/ruby_embedded_java_script.rb +200 -14
- data/lib/react_on_rails/shakapacker_config_helpers.rb +139 -0
- data/lib/react_on_rails/smart_error.rb +135 -5
- data/lib/react_on_rails/system_checker.rb +4 -53
- data/lib/react_on_rails/test_helper/dev_assets_detector.rb +4 -4
- data/lib/react_on_rails/test_helper.rb +5 -5
- data/lib/react_on_rails/version.rb +1 -1
- data/lib/react_on_rails/version_synchronizer.rb +22 -22
- data/lib/tasks/doctor.rake +5 -2
- data/lib/tasks/locale.rake +1 -1
- data/lib/tasks/sync_versions.rake +1 -1
- data/rakelib/lint.rake +15 -2
- data/rakelib/run_rspec.rake +1 -1
- data/rakelib/task_helpers.rb +1 -0
- data/rakelib/update_changelog.rake +2 -2
- data/sig/react_on_rails/helper.rbs +3 -0
- data/sig/react_on_rails/smart_error.rbs +25 -2
- metadata +14 -5
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 0630c2730fc7389d31e1f99d88e8df8dc6eb18b670e2909afd25e1e71b0c50ab
|
|
4
|
+
data.tar.gz: 6db462a802556e23085ae038585dd7c310c7710fd41a0a336d350383e85b9333
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 395e61f25e352b69df2788554dd0fcba85834df3e2f42bd768b6c2a81c8579e2d2504da1a47417abcc8ee5369181260c54f828e206f36dba409cbeef5a5818a9
|
|
7
|
+
data.tar.gz: 5ee8406c0ce296a4dca6e38299c1274fd2e5b6dde7f62f83f9790122dfc44cf88c0ca2cc093a9da7089265e53ed08ded28a018efc1929828c3a3ced000ba41c4
|
data/.rubocop.yml
CHANGED
|
@@ -11,6 +11,7 @@ AllCops:
|
|
|
11
11
|
|
|
12
12
|
Exclude:
|
|
13
13
|
- 'spec/dummy/bin/*'
|
|
14
|
+
- 'spec/react_on_rails/dummy-for-generators/**/*' # Generated fixture contains intentionally invalid Ruby
|
|
14
15
|
- 'spike/**/*' # Exploratory spike code outside lib/ — not part of the production surface
|
|
15
16
|
|
|
16
17
|
Naming/FileName:
|
data/Gemfile.lock
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
PATH
|
|
2
2
|
remote: .
|
|
3
3
|
specs:
|
|
4
|
-
react_on_rails (17.0.0.rc.
|
|
4
|
+
react_on_rails (17.0.0.rc.3)
|
|
5
5
|
addressable
|
|
6
6
|
connection_pool
|
|
7
7
|
execjs (~> 2.5)
|
|
@@ -207,7 +207,6 @@ GEM
|
|
|
207
207
|
racc (~> 1.4)
|
|
208
208
|
ostruct (0.6.1)
|
|
209
209
|
package_json (0.2.0)
|
|
210
|
-
parallel (1.24.0)
|
|
211
210
|
parser (3.3.1.0)
|
|
212
211
|
ast (~> 2.4.1)
|
|
213
212
|
racc
|
|
@@ -313,34 +312,6 @@ GEM
|
|
|
313
312
|
rspec-support (3.13.1)
|
|
314
313
|
rspec_junit_formatter (0.6.0)
|
|
315
314
|
rspec-core (>= 2, < 4, != 2.12.0)
|
|
316
|
-
rubocop (1.61.0)
|
|
317
|
-
json (~> 2.3)
|
|
318
|
-
language_server-protocol (>= 3.17.0)
|
|
319
|
-
parallel (~> 1.10)
|
|
320
|
-
parser (>= 3.3.0.2)
|
|
321
|
-
rainbow (>= 2.2.2, < 4.0)
|
|
322
|
-
regexp_parser (>= 1.8, < 3.0)
|
|
323
|
-
rexml (>= 3.2.5, < 4.0)
|
|
324
|
-
rubocop-ast (>= 1.30.0, < 2.0)
|
|
325
|
-
ruby-progressbar (~> 1.7)
|
|
326
|
-
unicode-display_width (>= 2.4.0, < 3.0)
|
|
327
|
-
rubocop-ast (1.31.3)
|
|
328
|
-
parser (>= 3.3.1.0)
|
|
329
|
-
rubocop-capybara (2.20.0)
|
|
330
|
-
rubocop (~> 1.41)
|
|
331
|
-
rubocop-factory_bot (2.25.1)
|
|
332
|
-
rubocop (~> 1.41)
|
|
333
|
-
rubocop-performance (1.20.2)
|
|
334
|
-
rubocop (>= 1.48.1, < 2.0)
|
|
335
|
-
rubocop-ast (>= 1.30.0, < 2.0)
|
|
336
|
-
rubocop-rspec (2.29.2)
|
|
337
|
-
rubocop (~> 1.40)
|
|
338
|
-
rubocop-capybara (~> 2.17)
|
|
339
|
-
rubocop-factory_bot (~> 2.22)
|
|
340
|
-
rubocop-rspec_rails (~> 2.28)
|
|
341
|
-
rubocop-rspec_rails (2.28.3)
|
|
342
|
-
rubocop (~> 1.40)
|
|
343
|
-
ruby-progressbar (1.13.0)
|
|
344
315
|
rubyzip (2.3.2)
|
|
345
316
|
sass-rails (6.0.0)
|
|
346
317
|
sassc-rails (~> 2.1, >= 2.1.1)
|
|
@@ -469,9 +440,6 @@ DEPENDENCIES
|
|
|
469
440
|
rspec-rails
|
|
470
441
|
rspec-retry
|
|
471
442
|
rspec_junit_formatter
|
|
472
|
-
rubocop (= 1.61.0)
|
|
473
|
-
rubocop-performance (~> 1.20.0)
|
|
474
|
-
rubocop-rspec (~> 2.26)
|
|
475
443
|
sass-rails (~> 6.0)
|
|
476
444
|
sdoc
|
|
477
445
|
selenium-webdriver (= 4.9.0)
|
data/Rakefile
CHANGED
|
@@ -13,7 +13,7 @@ desc "All actions but no examples, good for local developer run."
|
|
|
13
13
|
task all_but_examples: ["run_rspec:all_but_examples", "lint"]
|
|
14
14
|
|
|
15
15
|
desc "Prepare for ci, including node_package, dummy app, and generator examples"
|
|
16
|
-
task
|
|
16
|
+
task(prepare_for_ci:)
|
|
17
17
|
|
|
18
18
|
desc "Runs prepare_for_ci and tasks"
|
|
19
19
|
task ci: [:prepare_for_ci, *tasks]
|
data/lib/generators/USAGE
CHANGED
|
@@ -8,6 +8,12 @@ The react_on_rails:install generator integrates a React frontend, including SSR,
|
|
|
8
8
|
to integrate the Redux state container framework. The necessary node modules
|
|
9
9
|
will be automatically included for you.
|
|
10
10
|
|
|
11
|
+
* Tailwind CSS v4 (Optional)
|
|
12
|
+
|
|
13
|
+
Passing the --tailwind generator option installs Tailwind CSS v4, configures
|
|
14
|
+
the PostCSS plugin for Webpack or Rspack, and styles the generated
|
|
15
|
+
server-rendered Hello World example.
|
|
16
|
+
|
|
11
17
|
*******************************************************************************
|
|
12
18
|
|
|
13
19
|
After running the generator, you will want to:
|
|
@@ -41,6 +41,12 @@ module ReactOnRails
|
|
|
41
41
|
type: :boolean,
|
|
42
42
|
desc: "Use Webpack as the bundler (alias for --no-rspack; --no-webpack is equivalent to --rspack)"
|
|
43
43
|
|
|
44
|
+
# --tailwind
|
|
45
|
+
class_option :tailwind,
|
|
46
|
+
type: :boolean,
|
|
47
|
+
default: false,
|
|
48
|
+
desc: "Install Tailwind CSS v4 and style the generated SSR example"
|
|
49
|
+
|
|
44
50
|
# --pro
|
|
45
51
|
class_option :pro,
|
|
46
52
|
type: :boolean,
|
|
@@ -106,9 +112,21 @@ module ReactOnRails
|
|
|
106
112
|
generator.__send__(:use_rsc?)
|
|
107
113
|
end
|
|
108
114
|
|
|
115
|
+
def use_tailwind?
|
|
116
|
+
generator.__send__(:use_tailwind?)
|
|
117
|
+
end
|
|
118
|
+
|
|
109
119
|
def shakapacker_version_9_or_higher?
|
|
110
120
|
generator.__send__(:shakapacker_version_9_or_higher?)
|
|
111
121
|
end
|
|
122
|
+
|
|
123
|
+
def rsc_plugin_class_name
|
|
124
|
+
generator.__send__(:rsc_plugin_class_name)
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def rsc_plugin_import_path
|
|
128
|
+
generator.__send__(:rsc_plugin_import_path)
|
|
129
|
+
end
|
|
112
130
|
end
|
|
113
131
|
|
|
114
132
|
REMOVABLE_WEBPACK_FILES = (MANAGED_WEBPACK_FILE_TEMPLATES.keys +
|
|
@@ -220,7 +238,7 @@ module ReactOnRails
|
|
|
220
238
|
base_files = %w[app/javascript/packs/server-bundle.js]
|
|
221
239
|
|
|
222
240
|
# Skip HelloWorld CSS for Redux (uses HelloWorldApp) or RSC (uses HelloServer)
|
|
223
|
-
unless options.redux? || use_rsc?
|
|
241
|
+
unless options.redux? || use_rsc? || use_tailwind?
|
|
224
242
|
base_files << "app/javascript/src/HelloWorld/ror_components/HelloWorld.module.css"
|
|
225
243
|
end
|
|
226
244
|
|
|
@@ -251,6 +269,14 @@ module ReactOnRails
|
|
|
251
269
|
copy_webpack_main_config(base_path, config)
|
|
252
270
|
end
|
|
253
271
|
|
|
272
|
+
def copy_tailwind_files
|
|
273
|
+
return unless use_tailwind?
|
|
274
|
+
|
|
275
|
+
base_path = "base/tailwind/"
|
|
276
|
+
copy_file("#{base_path}app/javascript/stylesheets/application.css",
|
|
277
|
+
"app/javascript/stylesheets/application.css")
|
|
278
|
+
end
|
|
279
|
+
|
|
254
280
|
def copy_packer_config
|
|
255
281
|
base_path = "base/base/"
|
|
256
282
|
config = "config/shakapacker.yml"
|
|
@@ -372,8 +398,8 @@ module ReactOnRails
|
|
|
372
398
|
end
|
|
373
399
|
|
|
374
400
|
{
|
|
375
|
-
app_name
|
|
376
|
-
docs_url
|
|
401
|
+
app_name:,
|
|
402
|
+
docs_url:,
|
|
377
403
|
examples: home_page_examples,
|
|
378
404
|
file_hints: home_page_file_hints,
|
|
379
405
|
stack_badges: home_page_stack_badges,
|
|
@@ -962,9 +988,10 @@ module ReactOnRails
|
|
|
962
988
|
# Note: files originally generated with --pro or --rsc will not match when the
|
|
963
989
|
# current run omits those options; in that case, we preserve the directory.
|
|
964
990
|
# Templates rely on config[:message] plus a small helper subset exposed by
|
|
965
|
-
# TemplateRenderContext (add_documentation_reference, use_pro?, use_rsc?,
|
|
966
|
-
# shakapacker_version_9_or_higher
|
|
967
|
-
# NoMethodError and are caught below, treating the
|
|
991
|
+
# TemplateRenderContext (add_documentation_reference, use_pro?, use_rsc?, use_tailwind?,
|
|
992
|
+
# shakapacker_version_9_or_higher?, rsc_plugin_class_name, rsc_plugin_import_path).
|
|
993
|
+
# Missing method delegates raise NoMethodError and are caught below, treating the
|
|
994
|
+
# file as non-removable.
|
|
968
995
|
# Missing config hash keys return nil silently, so any new config key
|
|
969
996
|
# required by templates must be added to template_doc_config above.
|
|
970
997
|
# Use TemplateRenderContext#erb_binding to avoid leaking method-local
|
|
@@ -1009,13 +1036,13 @@ module ReactOnRails
|
|
|
1009
1036
|
expected_configs = shakapacker_default_configs
|
|
1010
1037
|
|
|
1011
1038
|
# Check if the content matches any of the known default configurations
|
|
1012
|
-
expected_configs.any? { |config| content_matches_template?(content, config, strip_comments:
|
|
1039
|
+
expected_configs.any? { |config| content_matches_template?(content, config, strip_comments:) }
|
|
1013
1040
|
end
|
|
1014
1041
|
|
|
1015
1042
|
def content_matches_template?(content, template, strip_comments: false)
|
|
1016
1043
|
# Normalize whitespace and compare
|
|
1017
|
-
normalize_config_content(content, strip_comments:
|
|
1018
|
-
normalize_config_content(template, strip_comments:
|
|
1044
|
+
normalize_config_content(content, strip_comments:) ==
|
|
1045
|
+
normalize_config_content(template, strip_comments:)
|
|
1019
1046
|
end
|
|
1020
1047
|
|
|
1021
1048
|
def normalize_config_content(content, strip_comments: false)
|
|
@@ -1221,8 +1248,9 @@ module ReactOnRails
|
|
|
1221
1248
|
|
|
1222
1249
|
content = File.read(shakapacker_config_path)
|
|
1223
1250
|
|
|
1224
|
-
#
|
|
1225
|
-
|
|
1251
|
+
# Don't materialize placeholders when any placeholder section already has
|
|
1252
|
+
# a direct or inherited active precompile_hook.
|
|
1253
|
+
return if active_precompile_hook_configured?(content)
|
|
1226
1254
|
|
|
1227
1255
|
# Replace the commented placeholder with the actual value
|
|
1228
1256
|
# Shakapacker 9.x default config has: # precompile_hook: ~
|
|
@@ -5,12 +5,12 @@ module ReactOnRails
|
|
|
5
5
|
module DemoPageConfig # rubocop:disable Metrics/ModuleLength
|
|
6
6
|
def build_hello_world_view_config(component_name:, source_path:, landing_page:, redux:, rsc_demo:)
|
|
7
7
|
{
|
|
8
|
-
component_name
|
|
8
|
+
component_name:,
|
|
9
9
|
title: redux ? "Redux SSR Demo" : "React SSR Demo",
|
|
10
|
-
intro: hello_world_intro(redux:
|
|
11
|
-
highlights: hello_world_highlights(redux:
|
|
12
|
-
file_hints: hello_world_file_hints(source_path
|
|
13
|
-
quick_links: hello_world_quick_links(landing_page
|
|
10
|
+
intro: hello_world_intro(redux:),
|
|
11
|
+
highlights: hello_world_highlights(redux:),
|
|
12
|
+
file_hints: hello_world_file_hints(source_path:, redux:),
|
|
13
|
+
quick_links: hello_world_quick_links(landing_page:, rsc_demo:),
|
|
14
14
|
learning_links: hello_world_learning_links
|
|
15
15
|
}
|
|
16
16
|
end
|
|
@@ -22,7 +22,7 @@ module ReactOnRails
|
|
|
22
22
|
"component response while only client islands ship JavaScript to the browser.",
|
|
23
23
|
highlights: hello_server_highlights,
|
|
24
24
|
file_hints: hello_server_file_hints,
|
|
25
|
-
quick_links: hello_server_quick_links(landing_page
|
|
25
|
+
quick_links: hello_server_quick_links(landing_page:, redux_demo:),
|
|
26
26
|
learning_links: hello_server_learning_links
|
|
27
27
|
}
|
|
28
28
|
end
|
|
@@ -41,7 +41,7 @@ module GeneratorHelper
|
|
|
41
41
|
result != false
|
|
42
42
|
rescue StandardError => e
|
|
43
43
|
say_status :warning, "Could not add packages via package_json gem: #{e.message}", :yellow
|
|
44
|
-
say_status :warning, "Will fall back to direct
|
|
44
|
+
say_status :warning, "Will fall back to direct package manager commands.", :yellow
|
|
45
45
|
false
|
|
46
46
|
end
|
|
47
47
|
end
|
|
@@ -121,6 +121,13 @@ module GeneratorHelper
|
|
|
121
121
|
options[:rsc]
|
|
122
122
|
end
|
|
123
123
|
|
|
124
|
+
# Check if Tailwind CSS should be installed and wired into the generated example.
|
|
125
|
+
#
|
|
126
|
+
# @return [Boolean] true if --tailwind is set
|
|
127
|
+
def use_tailwind?
|
|
128
|
+
options[:tailwind]
|
|
129
|
+
end
|
|
130
|
+
|
|
124
131
|
# Determine if the project is using rspack as the bundler.
|
|
125
132
|
#
|
|
126
133
|
# Detection priority:
|
|
@@ -197,6 +204,26 @@ module GeneratorHelper
|
|
|
197
204
|
path.sub(%r{\Aconfig/webpack/}, "config/rspack/")
|
|
198
205
|
end
|
|
199
206
|
|
|
207
|
+
# RSC client-manifest plugin class name for the active bundler.
|
|
208
|
+
# Rspack uses the native `RSCRspackPlugin`; webpack uses `RSCWebpackPlugin`.
|
|
209
|
+
# Both expose the same `{ isServer, clientReferences }` API and emit the same
|
|
210
|
+
# manifest schema, so only the import path and class name differ.
|
|
211
|
+
# Shared by the base webpack-config templates and the standalone RSC migration
|
|
212
|
+
# so both paths scaffold the bundler-correct plugin from one source of truth.
|
|
213
|
+
#
|
|
214
|
+
# @return [String] "RSCRspackPlugin" when rspack, "RSCWebpackPlugin" otherwise
|
|
215
|
+
def rsc_plugin_class_name
|
|
216
|
+
using_rspack? ? "RSCRspackPlugin" : "RSCWebpackPlugin"
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
# `react-on-rails-rsc` subpath that exports {#rsc_plugin_class_name}.
|
|
220
|
+
#
|
|
221
|
+
# @return [String] "react-on-rails-rsc/RspackPlugin" when rspack,
|
|
222
|
+
# "react-on-rails-rsc/WebpackPlugin" otherwise
|
|
223
|
+
def rsc_plugin_import_path
|
|
224
|
+
using_rspack? ? "react-on-rails-rsc/RspackPlugin" : "react-on-rails-rsc/WebpackPlugin"
|
|
225
|
+
end
|
|
226
|
+
|
|
200
227
|
# Detect the installed React version from package.json
|
|
201
228
|
# Uses VERSION_PARTS_REGEX pattern from VersionChecker for consistency
|
|
202
229
|
#
|
|
@@ -319,6 +346,24 @@ module GeneratorHelper
|
|
|
319
346
|
@pro_gem_install_deferred = true
|
|
320
347
|
end
|
|
321
348
|
|
|
349
|
+
# The other bundler's plugin class name — the one this project should NOT be using.
|
|
350
|
+
# Used to detect a config left in a mixed state (e.g. a legacy `RSCWebpackPlugin` surviving
|
|
351
|
+
# in an rspack project) so diagnostics can say "wrong bundler plugin" rather than "missing".
|
|
352
|
+
#
|
|
353
|
+
# @return [String] "RSCWebpackPlugin" when rspack, "RSCRspackPlugin" otherwise
|
|
354
|
+
def inactive_rsc_plugin_class_name
|
|
355
|
+
using_rspack? ? "RSCWebpackPlugin" : "RSCRspackPlugin"
|
|
356
|
+
end
|
|
357
|
+
|
|
358
|
+
# Import path for the inactive bundler's plugin — the counterpart to {#rsc_plugin_import_path},
|
|
359
|
+
# used when migrating a legacy config to the active bundler's plugin.
|
|
360
|
+
#
|
|
361
|
+
# @return [String] "react-on-rails-rsc/WebpackPlugin" when rspack,
|
|
362
|
+
# "react-on-rails-rsc/RspackPlugin" otherwise
|
|
363
|
+
def inactive_rsc_plugin_import_path
|
|
364
|
+
using_rspack? ? "react-on-rails-rsc/WebpackPlugin" : "react-on-rails-rsc/RspackPlugin"
|
|
365
|
+
end
|
|
366
|
+
|
|
322
367
|
# NOTE: only the `default:` section is inspected — same assumption as
|
|
323
368
|
# rspack_configured_in_project?. Projects that set `javascript_transpiler`
|
|
324
369
|
# only in per-environment sections (without a `default:` block) will not be
|
|
@@ -15,7 +15,7 @@ module GeneratorMessages
|
|
|
15
15
|
# Read package.json once and reuse for both package-manager detection and the
|
|
16
16
|
# build:test script presence check to avoid a second I/O pass.
|
|
17
17
|
package_json = read_package_json(app_root)
|
|
18
|
-
package_manager = detect_package_manager(app_root
|
|
18
|
+
package_manager = detect_package_manager(app_root:, package_json:)
|
|
19
19
|
ci_status = if ci_workflow_generated
|
|
20
20
|
"A GitHub Actions workflow has been generated at .github/workflows/ci.yml."
|
|
21
21
|
else
|
|
@@ -30,7 +30,7 @@ module GeneratorMessages
|
|
|
30
30
|
end
|
|
31
31
|
manual_build_command = shakapacker_build_command(
|
|
32
32
|
env: "RAILS_ENV=test NODE_ENV=test",
|
|
33
|
-
app_root
|
|
33
|
+
app_root:,
|
|
34
34
|
environment: "test"
|
|
35
35
|
)
|
|
36
36
|
|
|
@@ -37,8 +37,8 @@ module GeneratorMessages
|
|
|
37
37
|
# wants detection to fall through directly to lockfile heuristics.
|
|
38
38
|
def detect_package_manager(app_root: Dir.pwd, package_json: PACKAGE_JSON_UNSET)
|
|
39
39
|
detect_package_manager_with_source(
|
|
40
|
-
app_root
|
|
41
|
-
package_json:
|
|
40
|
+
app_root:,
|
|
41
|
+
package_json:
|
|
42
42
|
).first
|
|
43
43
|
end
|
|
44
44
|
|
|
@@ -52,13 +52,13 @@ module GeneratorMessages
|
|
|
52
52
|
return [env_package_manager, :env] if supported_package_manager?(env_package_manager)
|
|
53
53
|
|
|
54
54
|
content = package_json_content(
|
|
55
|
-
app_root
|
|
56
|
-
package_json:
|
|
55
|
+
app_root:,
|
|
56
|
+
package_json:
|
|
57
57
|
)
|
|
58
58
|
pm_from_json = content ? package_manager_name_from_content(content) : nil
|
|
59
59
|
return [pm_from_json, :package_json] if pm_from_json
|
|
60
60
|
|
|
61
|
-
pm_from_lockfile = detect_package_manager_from_lockfiles(app_root:
|
|
61
|
+
pm_from_lockfile = detect_package_manager_from_lockfiles(app_root:)
|
|
62
62
|
return [pm_from_lockfile, :lockfile] if pm_from_lockfile
|
|
63
63
|
|
|
64
64
|
["npm", :default]
|
|
@@ -84,8 +84,8 @@ module GeneratorMessages
|
|
|
84
84
|
# package_json: nil to preserve a cached missing/unreadable read.
|
|
85
85
|
def package_manager_declared?(manager:, app_root: Dir.pwd, package_json: PACKAGE_JSON_UNSET)
|
|
86
86
|
content = package_json_content(
|
|
87
|
-
app_root
|
|
88
|
-
package_json:
|
|
87
|
+
app_root:,
|
|
88
|
+
package_json:
|
|
89
89
|
)
|
|
90
90
|
return false unless content
|
|
91
91
|
|
|
@@ -99,12 +99,12 @@ module GeneratorMessages
|
|
|
99
99
|
# that's not on disk (e.g. `packageManager: pnpm` without `pnpm-lock.yaml`, which
|
|
100
100
|
# breaks `actions/setup-node`'s cache step).
|
|
101
101
|
def lockfile_for_manager?(package_manager, app_root: Dir.pwd)
|
|
102
|
-
!lockfile_filename_for(package_manager, app_root:
|
|
102
|
+
!lockfile_filename_for(package_manager, app_root:).nil?
|
|
103
103
|
end
|
|
104
104
|
|
|
105
105
|
def detect_package_manager_from_lockfiles(app_root: Dir.pwd)
|
|
106
106
|
LOCKFILE_CANDIDATES_BY_MANAGER.keys.find do |pm|
|
|
107
|
-
lockfile_for_manager?(pm, app_root:
|
|
107
|
+
lockfile_for_manager?(pm, app_root:)
|
|
108
108
|
end
|
|
109
109
|
end
|
|
110
110
|
|
|
@@ -7,7 +7,7 @@ module GeneratorMessages
|
|
|
7
7
|
private
|
|
8
8
|
|
|
9
9
|
def build_shakapacker_status_section(shakapacker_just_installed: false, app_root: Dir.pwd)
|
|
10
|
-
version_warning = check_shakapacker_version_warning(app_root:
|
|
10
|
+
version_warning = check_shakapacker_version_warning(app_root:)
|
|
11
11
|
if shakapacker_just_installed
|
|
12
12
|
base = <<~SHAKAPACKER
|
|
13
13
|
|
|
@@ -58,13 +58,13 @@ module GeneratorMessages
|
|
|
58
58
|
rsc: false, shakapacker_just_installed: false, landing_page: false,
|
|
59
59
|
ci_workflow_generated: false, app_root: Dir.pwd)
|
|
60
60
|
process_manager_section = build_process_manager_section
|
|
61
|
-
testing_section = build_testing_section(app_root:
|
|
62
|
-
ci_section = build_ci_section(app_root
|
|
63
|
-
package_manager = detect_package_manager(app_root:
|
|
64
|
-
shakapacker_status = build_shakapacker_status_section(shakapacker_just_installed
|
|
65
|
-
app_root:
|
|
66
|
-
render_example = build_render_example(component_name
|
|
67
|
-
render_label = build_render_label(route
|
|
61
|
+
testing_section = build_testing_section(app_root:)
|
|
62
|
+
ci_section = build_ci_section(app_root:, ci_workflow_generated:)
|
|
63
|
+
package_manager = detect_package_manager(app_root:)
|
|
64
|
+
shakapacker_status = build_shakapacker_status_section(shakapacker_just_installed:,
|
|
65
|
+
app_root:)
|
|
66
|
+
render_example = build_render_example(component_name:, route:, rsc:)
|
|
67
|
+
render_label = build_render_label(route:, rsc:)
|
|
68
68
|
normalized_route = route.to_s.sub(%r{\A/+}, "")
|
|
69
69
|
visit_url = if landing_page || normalized_route.empty?
|
|
70
70
|
"http://localhost:3000"
|
|
@@ -43,6 +43,12 @@ module ReactOnRails
|
|
|
43
43
|
desc: "Generate TypeScript files and install TypeScript dependencies. Default: false",
|
|
44
44
|
aliases: "-T"
|
|
45
45
|
|
|
46
|
+
# --tailwind
|
|
47
|
+
class_option :tailwind,
|
|
48
|
+
type: :boolean,
|
|
49
|
+
default: false,
|
|
50
|
+
desc: "Install Tailwind CSS v4 and style the generated SSR example. Default: false"
|
|
51
|
+
|
|
46
52
|
# --rspack / --no-rspack (Rspack is the default on fresh installs; --no-rspack selects Webpack)
|
|
47
53
|
# IMPORTANT: do NOT add a `default:` here. The absence of a default is load-bearing — Thor
|
|
48
54
|
# only includes :rspack in the options hash when the flag is explicitly passed, which is how
|
|
@@ -66,6 +72,15 @@ module ReactOnRails
|
|
|
66
72
|
default: false,
|
|
67
73
|
desc: "Skip warnings. Default: false"
|
|
68
74
|
|
|
75
|
+
# --agent-files / --no-agent-files
|
|
76
|
+
# Emits consumer-scoped AI-agent guidance (AGENTS.md) plus thin editor pointer
|
|
77
|
+
# files (CLAUDE.md, .cursor/rules/react-on-rails.mdc, .github/copilot-instructions.md).
|
|
78
|
+
# Default ON; pass --no-agent-files to skip. Existing files are never overwritten.
|
|
79
|
+
class_option :agent_files,
|
|
80
|
+
type: :boolean,
|
|
81
|
+
default: true,
|
|
82
|
+
desc: "Write AI-agent guidance files (AGENTS.md + editor pointers). Default: true"
|
|
83
|
+
|
|
69
84
|
# --pro
|
|
70
85
|
class_option :pro,
|
|
71
86
|
type: :boolean,
|
|
@@ -183,6 +198,7 @@ module ReactOnRails
|
|
|
183
198
|
add_package_json_scripts
|
|
184
199
|
add_ci_workflow
|
|
185
200
|
add_bin_scripts
|
|
201
|
+
add_agent_files
|
|
186
202
|
add_post_install_message
|
|
187
203
|
else
|
|
188
204
|
error = <<~MSG.strip
|
|
@@ -227,7 +243,7 @@ module ReactOnRails
|
|
|
227
243
|
# --pretend/--force/--skip must be forwarded explicitly at each boundary.
|
|
228
244
|
invoke "react_on_rails:base", [],
|
|
229
245
|
{ typescript: options.typescript?, redux: options.redux?, rspack: using_rspack?,
|
|
230
|
-
pro: use_pro?, rsc: use_rsc?, new_app: options.new_app?,
|
|
246
|
+
pro: use_pro?, rsc: use_rsc?, tailwind: use_tailwind?, new_app: options.new_app?,
|
|
231
247
|
shakapacker_just_installed: shakapacker_just_installed?,
|
|
232
248
|
force: options[:force], skip: options[:skip], pretend: options[:pretend] }
|
|
233
249
|
|
|
@@ -237,6 +253,7 @@ module ReactOnRails
|
|
|
237
253
|
# - Without --rsc: Normal behavior (HelloWorld or HelloWorldApp based on --redux)
|
|
238
254
|
if options.redux?
|
|
239
255
|
invoke "react_on_rails:react_with_redux", [], { typescript: options.typescript?,
|
|
256
|
+
tailwind: use_tailwind?,
|
|
240
257
|
invoked_by_install: true,
|
|
241
258
|
new_app: options.new_app?,
|
|
242
259
|
rsc: use_rsc?,
|
|
@@ -246,6 +263,7 @@ module ReactOnRails
|
|
|
246
263
|
# Only generate HelloWorld if RSC is not enabled
|
|
247
264
|
# For RSC, HelloServer replaces HelloWorld as the example component
|
|
248
265
|
invoke "react_on_rails:react_no_redux", [], { typescript: options.typescript?,
|
|
266
|
+
tailwind: use_tailwind?,
|
|
249
267
|
new_app: options.new_app?,
|
|
250
268
|
force: options[:force], skip: options[:skip],
|
|
251
269
|
pretend: options[:pretend] }
|
|
@@ -265,6 +283,7 @@ module ReactOnRails
|
|
|
265
283
|
|
|
266
284
|
invoke "react_on_rails:rsc", [], { typescript: options.typescript?, invoked_by_install: true,
|
|
267
285
|
new_app: options.new_app?, redux: options.redux?,
|
|
286
|
+
tailwind: use_tailwind?,
|
|
268
287
|
force: options[:force], skip: options[:skip],
|
|
269
288
|
pretend: options[:pretend] }
|
|
270
289
|
end
|
|
@@ -294,7 +313,7 @@ module ReactOnRails
|
|
|
294
313
|
package_json = GeneratorMessages.read_package_json(destination_root)
|
|
295
314
|
package_manager = GeneratorMessages.detect_package_manager(
|
|
296
315
|
app_root: destination_root,
|
|
297
|
-
package_json:
|
|
316
|
+
package_json:
|
|
298
317
|
)
|
|
299
318
|
# Scope the lockfile check to the detected manager: a generic "any lockfile exists" check
|
|
300
319
|
# would emit `cache: "pnpm"` in CI when only `yarn.lock` is on disk, breaking setup-node.
|
|
@@ -307,16 +326,16 @@ module ReactOnRails
|
|
|
307
326
|
GeneratorMessages.package_manager_declared?(
|
|
308
327
|
app_root: destination_root,
|
|
309
328
|
manager: "pnpm",
|
|
310
|
-
package_json:
|
|
329
|
+
package_json:
|
|
311
330
|
)
|
|
312
331
|
has_active_record = File.exist?(File.join(destination_root, "config/database.yml"))
|
|
313
332
|
has_rspec = File.exist?(File.join(destination_root, "spec/rails_helper.rb")) ||
|
|
314
333
|
File.exist?(File.join(destination_root, "spec/spec_helper.rb"))
|
|
315
334
|
template("templates/base/base/.github/workflows/ci.yml.tt", ci_path,
|
|
316
|
-
{ package_manager
|
|
317
|
-
pnpm_version_declared
|
|
335
|
+
{ package_manager:, has_lockfile:,
|
|
336
|
+
pnpm_version_declared:,
|
|
318
337
|
pnpm_fallback_version: CI_PNPM_FALLBACK_VERSION,
|
|
319
|
-
has_active_record
|
|
338
|
+
has_active_record:, has_rspec:,
|
|
320
339
|
precompile_hook_command: shakapacker_precompile_hook_command(environment: "test") })
|
|
321
340
|
@ci_workflow_generated = true
|
|
322
341
|
end
|
|
@@ -442,7 +461,7 @@ module ReactOnRails
|
|
|
442
461
|
content = JSON.parse(original_text)
|
|
443
462
|
content["scripts"] = existing_scripts.merge(scripts_to_add)
|
|
444
463
|
indent = original_text[/\A\{\n(\s+)/, 1] || " "
|
|
445
|
-
"#{JSON.pretty_generate(content, indent:
|
|
464
|
+
"#{JSON.pretty_generate(content, indent:)}\n"
|
|
446
465
|
end
|
|
447
466
|
|
|
448
467
|
def ensure_jsx_in_js_compatibility
|
|
@@ -622,6 +641,40 @@ module ReactOnRails
|
|
|
622
641
|
File.chmod(0o755, *files_to_become_executable)
|
|
623
642
|
end
|
|
624
643
|
|
|
644
|
+
# Consumer-scoped AI-agent guidance written into the generated app. The canonical
|
|
645
|
+
# AGENTS.md content lives in templates/agent_files/ and is the single source of truth;
|
|
646
|
+
# create-react-on-rails-app gets it for free because it delegates to this generator.
|
|
647
|
+
# Each file is copied only when absent so we never clobber an app's existing agent files.
|
|
648
|
+
AGENT_FILES = %w[
|
|
649
|
+
AGENTS.md
|
|
650
|
+
CLAUDE.md
|
|
651
|
+
.cursor/rules/react-on-rails.mdc
|
|
652
|
+
.github/copilot-instructions.md
|
|
653
|
+
].freeze
|
|
654
|
+
private_constant :AGENT_FILES
|
|
655
|
+
|
|
656
|
+
def add_agent_files
|
|
657
|
+
return unless options.agent_files?
|
|
658
|
+
|
|
659
|
+
# AGENTS.md is the canonical file the editor pointers (CLAUDE.md, Cursor, Copilot) all
|
|
660
|
+
# reference. If the app already has its own AGENTS.md, it may document unrelated
|
|
661
|
+
# conventions, so leave it untouched AND skip the pointer files rather than emit editor
|
|
662
|
+
# guidance pointing at an AGENTS.md we did not write.
|
|
663
|
+
if File.exist?(File.join(destination_root, "AGENTS.md"))
|
|
664
|
+
say_status :skip, "AGENTS.md already exists; leaving it and the editor pointer files untouched", :yellow
|
|
665
|
+
return
|
|
666
|
+
end
|
|
667
|
+
|
|
668
|
+
AGENT_FILES.each do |relative_path|
|
|
669
|
+
if File.exist?(File.join(destination_root, relative_path))
|
|
670
|
+
say_status :skip, "#{relative_path} already exists; leaving it untouched", :yellow
|
|
671
|
+
next
|
|
672
|
+
end
|
|
673
|
+
|
|
674
|
+
copy_file("templates/agent_files/#{relative_path}", relative_path)
|
|
675
|
+
end
|
|
676
|
+
end
|
|
677
|
+
|
|
625
678
|
def replace_stock_rails_bin_dev!
|
|
626
679
|
@preserve_existing_bin_dev = false
|
|
627
680
|
|
|
@@ -690,8 +743,8 @@ module ReactOnRails
|
|
|
690
743
|
end
|
|
691
744
|
|
|
692
745
|
GeneratorMessages.add_info(GeneratorMessages.helpful_message_after_installation(
|
|
693
|
-
component_name
|
|
694
|
-
route
|
|
746
|
+
component_name:,
|
|
747
|
+
route:,
|
|
695
748
|
pro: use_pro?,
|
|
696
749
|
rsc: use_rsc?,
|
|
697
750
|
shakapacker_just_installed: shakapacker_just_installed?,
|
|
@@ -722,6 +775,10 @@ module ReactOnRails
|
|
|
722
775
|
flags << "--pro"
|
|
723
776
|
end
|
|
724
777
|
|
|
778
|
+
# Preserve an explicit agent-files opt-out so the suggested re-run doesn't emit
|
|
779
|
+
# AGENTS.md/editor files a user deliberately skipped (--agent-files defaults to on).
|
|
780
|
+
flags << "--no-agent-files" unless options.agent_files?
|
|
781
|
+
|
|
725
782
|
["rails generate react_on_rails:install", *flags].join(" ")
|
|
726
783
|
end
|
|
727
784
|
|