react_on_rails 15.0.0.rc.2 → 16.0.1.rc.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +103 -34
- data/CLAUDE.md +102 -0
- data/CODING_AGENTS.md +312 -0
- data/CONTRIBUTING.md +378 -3
- data/Gemfile.lock +2 -1
- data/LICENSE.md +30 -4
- data/LICENSES/README.md +14 -0
- data/REACT-ON-RAILS-PRO-LICENSE.md +129 -0
- data/README.md +70 -20
- data/TODO.md +135 -0
- data/eslint.config.ts +5 -0
- data/knip.ts +20 -9
- data/lib/generators/USAGE +4 -5
- data/lib/generators/react_on_rails/USAGE +65 -0
- data/lib/generators/react_on_rails/base_generator.rb +263 -57
- data/lib/generators/react_on_rails/dev_tests_generator.rb +1 -0
- data/lib/generators/react_on_rails/generator_helper.rb +35 -1
- data/lib/generators/react_on_rails/generator_messages.rb +138 -17
- data/lib/generators/react_on_rails/install_generator.rb +336 -26
- data/lib/generators/react_on_rails/react_no_redux_generator.rb +19 -6
- data/lib/generators/react_on_rails/react_with_redux_generator.rb +111 -18
- data/lib/generators/react_on_rails/templates/base/base/Procfile.dev +5 -0
- data/lib/generators/react_on_rails/templates/base/base/Procfile.dev-prod-assets +8 -0
- data/lib/generators/react_on_rails/templates/base/base/Procfile.dev-static-assets +2 -0
- data/lib/generators/react_on_rails/templates/base/base/app/javascript/bundles/HelloWorld/components/HelloWorld.jsx +0 -5
- data/lib/generators/react_on_rails/templates/base/base/app/javascript/packs/server-bundle.js +1 -8
- data/lib/generators/react_on_rails/templates/base/base/app/javascript/src/HelloWorld/ror_components/HelloWorld.client.jsx +21 -0
- 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.module.css +4 -0
- data/lib/generators/react_on_rails/templates/base/base/app/javascript/src/HelloWorld/ror_components/HelloWorld.server.jsx +5 -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/app/views/hello_world/index.html.erb.tt +1 -1
- data/lib/generators/react_on_rails/templates/base/base/app/views/layouts/hello_world.html.erb +4 -2
- data/lib/generators/react_on_rails/templates/base/base/babel.config.js.tt +5 -2
- data/lib/generators/react_on_rails/templates/base/base/bin/dev +34 -0
- data/lib/generators/react_on_rails/templates/base/base/config/initializers/react_on_rails.rb.tt +3 -3
- data/lib/generators/react_on_rails/templates/base/base/config/shakapacker.yml +76 -7
- data/lib/generators/react_on_rails/templates/base/base/config/webpack/commonWebpackConfig.js.tt +1 -1
- data/lib/generators/react_on_rails/templates/base/base/config/webpack/development.js.tt +8 -8
- data/lib/generators/react_on_rails/templates/base/base/config/webpack/production.js.tt +2 -2
- data/lib/generators/react_on_rails/templates/base/base/config/webpack/test.js.tt +2 -2
- data/lib/generators/react_on_rails/templates/dev_tests/spec/system/hello_world_spec.rb +0 -2
- 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.jsx +0 -6
- data/lib/generators/react_on_rails/templates/redux/base/app/javascript/bundles/HelloWorld/components/HelloWorld.module.css +4 -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.jsx +5 -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 +15 -11
- data/lib/react_on_rails/controller.rb +5 -3
- data/lib/react_on_rails/dev/file_manager.rb +78 -0
- data/lib/react_on_rails/dev/pack_generator.rb +27 -0
- data/lib/react_on_rails/dev/process_manager.rb +61 -0
- data/lib/react_on_rails/dev/server_manager.rb +487 -0
- data/lib/react_on_rails/dev.rb +20 -0
- data/lib/react_on_rails/doctor.rb +1149 -0
- data/lib/react_on_rails/engine.rb +6 -0
- data/lib/react_on_rails/git_utils.rb +12 -2
- data/lib/react_on_rails/helper.rb +19 -44
- data/lib/react_on_rails/packer_utils.rb +4 -18
- data/lib/react_on_rails/packs_generator.rb +134 -8
- 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 +8 -4
- data/lib/react_on_rails/server_rendering_js_code.rb +0 -1
- data/lib/react_on_rails/server_rendering_pool/ruby_embedded_java_script.rb +1 -0
- data/lib/react_on_rails/system_checker.rb +659 -0
- data/lib/react_on_rails/test_helper/webpack_assets_status_checker.rb +1 -0
- data/lib/react_on_rails/utils.rb +16 -1
- data/lib/react_on_rails/version.rb +1 -1
- data/lib/react_on_rails/version_syntax_converter.rb +1 -1
- data/lib/react_on_rails.rb +1 -0
- data/lib/tasks/doctor.rake +51 -0
- data/lib/tasks/generate_packs.rake +144 -1
- data/package-lock.json +11984 -0
- data/react_on_rails.gemspec +1 -0
- metadata +55 -11
- data/REACT-ON-RAILS-PRO-LICENSE +0 -95
- data/lib/generators/react_on_rails/adapt_for_older_shakapacker_generator.rb +0 -41
- data/lib/generators/react_on_rails/bin/dev +0 -30
- data/lib/generators/react_on_rails/bin/dev-static +0 -30
- data/lib/generators/react_on_rails/templates/base/base/Procfile.dev-static.tt +0 -9
- data/lib/generators/react_on_rails/templates/base/base/Procfile.dev.tt +0 -5
- data/lib/generators/react_on_rails/templates/base/base/app/javascript/packs/registration.js.tt +0 -8
- /data/lib/generators/react_on_rails/templates/base/base/config/webpack/{webpackConfig.js.tt → generateWebpackConfigs.js.tt} +0 -0
- /data/lib/generators/react_on_rails/templates/redux/base/app/javascript/bundles/HelloWorld/startup/{HelloWorldApp.jsx → HelloWorldApp.client.jsx} +0 -0
@@ -1,11 +1,13 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require "rails/generators"
|
4
|
+
require "json"
|
4
5
|
require_relative "generator_helper"
|
5
6
|
require_relative "generator_messages"
|
6
7
|
|
7
8
|
module ReactOnRails
|
8
9
|
module Generators
|
10
|
+
# rubocop:disable Metrics/ClassLength
|
9
11
|
class InstallGenerator < Rails::Generators::Base
|
10
12
|
include GeneratorHelper
|
11
13
|
|
@@ -16,22 +18,38 @@ module ReactOnRails
|
|
16
18
|
class_option :redux,
|
17
19
|
type: :boolean,
|
18
20
|
default: false,
|
19
|
-
desc: "Install Redux
|
21
|
+
desc: "Install Redux package and Redux version of Hello World Example. Default: false",
|
20
22
|
aliases: "-R"
|
21
23
|
|
24
|
+
# --typescript
|
25
|
+
class_option :typescript,
|
26
|
+
type: :boolean,
|
27
|
+
default: false,
|
28
|
+
desc: "Generate TypeScript files and install TypeScript dependencies. Default: false",
|
29
|
+
aliases: "-T"
|
30
|
+
|
22
31
|
# --ignore-warnings
|
23
32
|
class_option :ignore_warnings,
|
24
33
|
type: :boolean,
|
25
34
|
default: false,
|
26
35
|
desc: "Skip warnings. Default: false"
|
27
36
|
|
37
|
+
# Removed: --skip-shakapacker-install (Shakapacker is now a required dependency)
|
38
|
+
|
28
39
|
def run_generators
|
29
40
|
if installation_prerequisites_met? || options.ignore_warnings?
|
30
41
|
invoke_generators
|
31
42
|
add_bin_scripts
|
32
43
|
add_post_install_message
|
33
44
|
else
|
34
|
-
error =
|
45
|
+
error = <<~MSG.strip
|
46
|
+
🚫 React on Rails generator prerequisites not met!
|
47
|
+
|
48
|
+
Please resolve the issues listed above before continuing.
|
49
|
+
All prerequisites must be satisfied for a successful installation.
|
50
|
+
|
51
|
+
Use --ignore-warnings to bypass checks (not recommended).
|
52
|
+
MSG
|
35
53
|
GeneratorMessages.add_error(error)
|
36
54
|
end
|
37
55
|
ensure
|
@@ -47,45 +65,97 @@ module ReactOnRails
|
|
47
65
|
end
|
48
66
|
|
49
67
|
def invoke_generators
|
50
|
-
|
68
|
+
ensure_shakapacker_installed
|
69
|
+
if options.typescript?
|
70
|
+
install_typescript_dependencies
|
71
|
+
create_css_module_types
|
72
|
+
create_typescript_config
|
73
|
+
end
|
74
|
+
invoke "react_on_rails:base", [], { typescript: options.typescript? }
|
51
75
|
if options.redux?
|
52
|
-
invoke "react_on_rails:react_with_redux"
|
76
|
+
invoke "react_on_rails:react_with_redux", [], { typescript: options.typescript? }
|
53
77
|
else
|
54
|
-
invoke "react_on_rails:react_no_redux"
|
78
|
+
invoke "react_on_rails:react_no_redux", [], { typescript: options.typescript? }
|
55
79
|
end
|
56
|
-
|
57
|
-
invoke "react_on_rails:adapt_for_older_shakapacker" unless using_shakapacker_7_or_above?
|
58
80
|
end
|
59
81
|
|
60
82
|
# NOTE: other requirements for existing files such as .gitignore or application.
|
61
83
|
# js(.coffee) are not checked by this method, but instead produce warning messages
|
62
84
|
# and allow the build to continue
|
63
85
|
def installation_prerequisites_met?
|
64
|
-
!(missing_node? ||
|
86
|
+
!(missing_node? || missing_package_manager? || ReactOnRails::GitUtils.uncommitted_changes?(GeneratorMessages))
|
65
87
|
end
|
66
88
|
|
67
|
-
def
|
68
|
-
|
89
|
+
def missing_node?
|
90
|
+
node_missing = ReactOnRails::Utils.running_on_windows? ? `where node`.blank? : `which node`.blank?
|
69
91
|
|
70
|
-
|
71
|
-
|
72
|
-
|
92
|
+
if node_missing
|
93
|
+
error = <<~MSG.strip
|
94
|
+
🚫 Node.js is required but not found on your system.
|
95
|
+
|
96
|
+
Please install Node.js before continuing:
|
97
|
+
• Download from: https://nodejs.org/en/
|
98
|
+
• Recommended: Use a version manager like nvm, fnm, or volta
|
99
|
+
• Minimum required version: Node.js 18+
|
100
|
+
|
101
|
+
After installation, restart your terminal and try again.
|
102
|
+
MSG
|
103
|
+
GeneratorMessages.add_error(error)
|
104
|
+
return true
|
105
|
+
end
|
106
|
+
|
107
|
+
# Check Node.js version if available
|
108
|
+
check_node_version
|
109
|
+
false
|
73
110
|
end
|
74
111
|
|
75
|
-
def
|
76
|
-
|
112
|
+
def check_node_version
|
113
|
+
node_version = `node --version 2>/dev/null`.strip
|
114
|
+
return if node_version.blank?
|
77
115
|
|
78
|
-
|
79
|
-
|
80
|
-
|
116
|
+
# Extract major version number (e.g., "v18.17.0" -> 18)
|
117
|
+
major_version = node_version[/v(\d+)/, 1]&.to_i
|
118
|
+
return unless major_version
|
119
|
+
|
120
|
+
return unless major_version < 18
|
121
|
+
|
122
|
+
warning = <<~MSG.strip
|
123
|
+
⚠️ Node.js version #{node_version} detected.
|
124
|
+
|
125
|
+
React on Rails recommends Node.js 18+ for best compatibility.
|
126
|
+
You may experience issues with older versions.
|
127
|
+
|
128
|
+
Consider upgrading: https://nodejs.org/en/
|
129
|
+
MSG
|
130
|
+
GeneratorMessages.add_warning(warning)
|
131
|
+
end
|
132
|
+
|
133
|
+
def ensure_shakapacker_installed
|
134
|
+
return if shakapacker_configured?
|
135
|
+
|
136
|
+
print_shakapacker_setup_banner
|
137
|
+
ensure_shakapacker_in_gemfile
|
138
|
+
install_shakapacker
|
139
|
+
finalize_shakapacker_setup
|
140
|
+
end
|
141
|
+
|
142
|
+
# Checks whether "shakapacker" is explicitly declared in this project's Gemfile.
|
143
|
+
# We only check the Gemfile text, not lockfile or dependencies, because
|
144
|
+
# shakapacker might be present as a dependency of react_on_rails but not
|
145
|
+
# properly configured for this specific Rails application.
|
146
|
+
def shakapacker_in_gemfile?
|
147
|
+
gem_name = "shakapacker"
|
148
|
+
shakapacker_in_gemfile_text?(gem_name)
|
81
149
|
end
|
82
150
|
|
83
151
|
def add_bin_scripts
|
84
|
-
|
152
|
+
# Copy bin scripts from templates
|
153
|
+
template_bin_path = "#{__dir__}/templates/base/base/bin"
|
154
|
+
directory template_bin_path, "bin"
|
85
155
|
|
86
156
|
# Make these and only these files executable
|
87
157
|
files_to_copy = []
|
88
|
-
Dir.chdir(
|
158
|
+
Dir.chdir(template_bin_path) do
|
89
159
|
files_to_copy.concat(Dir.glob("*"))
|
90
160
|
end
|
91
161
|
files_to_become_executable = files_to_copy.map { |filename| "bin/#{filename}" }
|
@@ -94,16 +164,256 @@ module ReactOnRails
|
|
94
164
|
end
|
95
165
|
|
96
166
|
def add_post_install_message
|
97
|
-
|
167
|
+
# Determine what route will be created by the generator
|
168
|
+
route = "hello_world" # This is the hardcoded route from base_generator.rb
|
169
|
+
component_name = options.redux? ? "HelloWorldApp" : "HelloWorld"
|
170
|
+
|
171
|
+
GeneratorMessages.add_info(GeneratorMessages.helpful_message_after_installation(
|
172
|
+
component_name: component_name,
|
173
|
+
route: route
|
174
|
+
))
|
175
|
+
end
|
176
|
+
|
177
|
+
def shakapacker_loaded_in_process?(gem_name)
|
178
|
+
Gem.loaded_specs.key?(gem_name)
|
179
|
+
end
|
180
|
+
|
181
|
+
def shakapacker_in_lockfile?(gem_name)
|
182
|
+
gemfile = ENV["BUNDLE_GEMFILE"] || "Gemfile"
|
183
|
+
lockfile = File.join(File.dirname(gemfile), "Gemfile.lock")
|
184
|
+
|
185
|
+
File.file?(lockfile) && File.foreach(lockfile).any? { |l| l.match?(/^\s{4}#{Regexp.escape(gem_name)}\s\(/) }
|
186
|
+
end
|
187
|
+
|
188
|
+
def shakapacker_in_bundler_specs?(gem_name)
|
189
|
+
require "bundler"
|
190
|
+
Bundler.load.specs.any? { |s| s.name == gem_name }
|
191
|
+
rescue StandardError
|
192
|
+
false
|
193
|
+
end
|
194
|
+
|
195
|
+
def shakapacker_in_gemfile_text?(gem_name)
|
196
|
+
gemfile = ENV["BUNDLE_GEMFILE"] || "Gemfile"
|
197
|
+
|
198
|
+
File.file?(gemfile) &&
|
199
|
+
File.foreach(gemfile).any? { |l| l.match?(/^\s*gem\s+['"]#{Regexp.escape(gem_name)}['"]/) }
|
200
|
+
end
|
201
|
+
|
202
|
+
def cli_exists?(command)
|
203
|
+
system("which #{command} > /dev/null 2>&1")
|
204
|
+
end
|
205
|
+
|
206
|
+
def shakapacker_binaries_exist?
|
207
|
+
File.exist?("bin/shakapacker") && File.exist?("bin/shakapacker-dev-server")
|
208
|
+
end
|
209
|
+
|
210
|
+
def shakapacker_configured?
|
211
|
+
# Check for essential shakapacker configuration files and binaries
|
212
|
+
shakapacker_binaries_exist? &&
|
213
|
+
File.exist?("config/shakapacker.yml") &&
|
214
|
+
File.exist?("config/webpack/webpack.config.js")
|
215
|
+
end
|
216
|
+
|
217
|
+
def print_shakapacker_setup_banner
|
218
|
+
puts Rainbow("\n#{'=' * 80}").cyan
|
219
|
+
puts Rainbow("🔧 SHAKAPACKER SETUP").cyan.bold
|
220
|
+
puts Rainbow("=" * 80).cyan
|
221
|
+
end
|
222
|
+
|
223
|
+
def ensure_shakapacker_in_gemfile
|
224
|
+
return if shakapacker_in_gemfile?
|
225
|
+
|
226
|
+
puts Rainbow("📝 Adding Shakapacker to Gemfile...").yellow
|
227
|
+
success = system("bundle add shakapacker --strict")
|
228
|
+
return if success
|
229
|
+
|
230
|
+
handle_shakapacker_gemfile_error
|
231
|
+
end
|
232
|
+
|
233
|
+
def install_shakapacker
|
234
|
+
puts Rainbow("⚙️ Installing Shakapacker (required for webpack integration)...").yellow
|
235
|
+
|
236
|
+
# First run bundle install to make shakapacker available
|
237
|
+
puts Rainbow("📦 Running bundle install...").yellow
|
238
|
+
bundle_success = system("bundle install")
|
239
|
+
unless bundle_success
|
240
|
+
handle_shakapacker_install_error
|
241
|
+
return
|
242
|
+
end
|
243
|
+
|
244
|
+
# Then run the shakapacker installer
|
245
|
+
success = system("bundle exec rails shakapacker:install")
|
246
|
+
return if success
|
247
|
+
|
248
|
+
handle_shakapacker_install_error
|
98
249
|
end
|
99
250
|
|
100
|
-
def
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
#
|
251
|
+
def finalize_shakapacker_setup
|
252
|
+
puts Rainbow("✅ Shakapacker installed successfully!").green
|
253
|
+
puts Rainbow("=" * 80).cyan
|
254
|
+
puts Rainbow("🚀 CONTINUING WITH REACT ON RAILS SETUP").cyan.bold
|
255
|
+
puts "#{Rainbow('=' * 80).cyan}\n"
|
256
|
+
|
257
|
+
# Create marker file so base generator can avoid copying shakapacker.yml
|
258
|
+
File.write(".shakapacker_just_installed", "")
|
259
|
+
end
|
260
|
+
|
261
|
+
def handle_shakapacker_gemfile_error
|
262
|
+
error = <<~MSG.strip
|
263
|
+
🚫 Failed to add Shakapacker to your Gemfile.
|
264
|
+
|
265
|
+
This could be due to:
|
266
|
+
• Bundle installation issues
|
267
|
+
• Network connectivity problems
|
268
|
+
• Gemfile permissions
|
269
|
+
|
270
|
+
Please try manually:
|
271
|
+
bundle add shakapacker --strict
|
272
|
+
|
273
|
+
Then re-run: rails generate react_on_rails:install
|
274
|
+
MSG
|
275
|
+
GeneratorMessages.add_error(error)
|
276
|
+
raise Thor::Error, error unless options.ignore_warnings?
|
277
|
+
end
|
278
|
+
|
279
|
+
def handle_shakapacker_install_error
|
280
|
+
error = <<~MSG.strip
|
281
|
+
🚫 Failed to install Shakapacker automatically.
|
282
|
+
|
283
|
+
This could be due to:
|
284
|
+
• Missing Node.js or npm/yarn
|
285
|
+
• Network connectivity issues
|
286
|
+
• Incomplete bundle installation
|
287
|
+
• Missing write permissions
|
288
|
+
|
289
|
+
Troubleshooting steps:
|
290
|
+
1. Ensure Node.js is installed: node --version
|
291
|
+
2. Run: bundle install
|
292
|
+
3. Try manually: bundle exec rails shakapacker:install
|
293
|
+
4. Check for error output above
|
294
|
+
5. Re-run: rails generate react_on_rails:install
|
295
|
+
|
296
|
+
Need help? Visit: https://github.com/shakacode/shakapacker/blob/main/docs/installation.md
|
297
|
+
MSG
|
298
|
+
GeneratorMessages.add_error(error)
|
299
|
+
raise Thor::Error, error unless options.ignore_warnings?
|
300
|
+
end
|
301
|
+
|
302
|
+
def missing_package_manager?
|
303
|
+
package_managers = %w[npm pnpm yarn bun]
|
304
|
+
missing = package_managers.none? { |pm| cli_exists?(pm) }
|
305
|
+
|
306
|
+
if missing
|
307
|
+
error = <<~MSG.strip
|
308
|
+
🚫 No JavaScript package manager found on your system.
|
309
|
+
|
310
|
+
React on Rails requires a JavaScript package manager to install dependencies.
|
311
|
+
Please install one of the following:
|
312
|
+
|
313
|
+
• npm: Usually comes with Node.js (https://nodejs.org/en/)
|
314
|
+
• yarn: npm install -g yarn (https://yarnpkg.com/)
|
315
|
+
• pnpm: npm install -g pnpm (https://pnpm.io/)
|
316
|
+
• bun: Install from https://bun.sh/
|
317
|
+
|
318
|
+
After installation, restart your terminal and try again.
|
319
|
+
MSG
|
320
|
+
GeneratorMessages.add_error(error)
|
321
|
+
return true
|
322
|
+
end
|
323
|
+
|
105
324
|
false
|
106
325
|
end
|
326
|
+
|
327
|
+
def install_typescript_dependencies
|
328
|
+
puts Rainbow("📝 Installing TypeScript dependencies...").yellow
|
329
|
+
|
330
|
+
# Install TypeScript and React type definitions
|
331
|
+
typescript_packages = %w[
|
332
|
+
typescript
|
333
|
+
@types/react
|
334
|
+
@types/react-dom
|
335
|
+
@babel/preset-typescript
|
336
|
+
]
|
337
|
+
|
338
|
+
# Try using GeneratorHelper first (package manager agnostic)
|
339
|
+
return if add_npm_dependencies(typescript_packages, dev: true)
|
340
|
+
|
341
|
+
# Fallback to npm if GeneratorHelper fails
|
342
|
+
success = system("npm", "install", "--save-dev", *typescript_packages)
|
343
|
+
return if success
|
344
|
+
|
345
|
+
warning = <<~MSG.strip
|
346
|
+
⚠️ Failed to install TypeScript dependencies automatically.
|
347
|
+
|
348
|
+
Please run manually:
|
349
|
+
npm install --save-dev #{typescript_packages.join(' ')}
|
350
|
+
MSG
|
351
|
+
GeneratorMessages.add_warning(warning)
|
352
|
+
end
|
353
|
+
|
354
|
+
def create_css_module_types
|
355
|
+
puts Rainbow("📝 Creating CSS module type definitions...").yellow
|
356
|
+
|
357
|
+
# Ensure the types directory exists
|
358
|
+
FileUtils.mkdir_p("app/javascript/types")
|
359
|
+
|
360
|
+
css_module_types_content = <<~TS.strip
|
361
|
+
// TypeScript definitions for CSS modules
|
362
|
+
declare module "*.module.css" {
|
363
|
+
const classes: { [key: string]: string };
|
364
|
+
export default classes;
|
365
|
+
}
|
366
|
+
|
367
|
+
declare module "*.module.scss" {
|
368
|
+
const classes: { [key: string]: string };
|
369
|
+
export default classes;
|
370
|
+
}
|
371
|
+
|
372
|
+
declare module "*.module.sass" {
|
373
|
+
const classes: { [key: string]: string };
|
374
|
+
export default classes;
|
375
|
+
}
|
376
|
+
TS
|
377
|
+
|
378
|
+
File.write("app/javascript/types/css-modules.d.ts", css_module_types_content)
|
379
|
+
puts Rainbow("✅ Created CSS module type definitions").green
|
380
|
+
end
|
381
|
+
|
382
|
+
def create_typescript_config
|
383
|
+
if File.exist?("tsconfig.json")
|
384
|
+
puts Rainbow("⚠️ tsconfig.json already exists, skipping creation").yellow
|
385
|
+
return
|
386
|
+
end
|
387
|
+
|
388
|
+
tsconfig_content = {
|
389
|
+
"compilerOptions" => {
|
390
|
+
"target" => "es2018",
|
391
|
+
"allowJs" => true,
|
392
|
+
"skipLibCheck" => true,
|
393
|
+
"strict" => true,
|
394
|
+
"noUncheckedIndexedAccess" => true,
|
395
|
+
"forceConsistentCasingInFileNames" => true,
|
396
|
+
"noFallthroughCasesInSwitch" => true,
|
397
|
+
"module" => "esnext",
|
398
|
+
"moduleResolution" => "bundler",
|
399
|
+
"resolveJsonModule" => true,
|
400
|
+
"isolatedModules" => true,
|
401
|
+
"noEmit" => true,
|
402
|
+
"jsx" => "react-jsx"
|
403
|
+
},
|
404
|
+
"include" => [
|
405
|
+
"app/javascript/**/*"
|
406
|
+
]
|
407
|
+
}
|
408
|
+
|
409
|
+
File.write("tsconfig.json", JSON.pretty_generate(tsconfig_content))
|
410
|
+
puts Rainbow("✅ Created tsconfig.json").green
|
411
|
+
end
|
412
|
+
|
413
|
+
# Removed: Shakapacker auto-installation logic (now explicit dependency)
|
414
|
+
|
415
|
+
# Removed: Shakapacker 8+ is now required as explicit dependency
|
416
|
+
# rubocop:enable Metrics/ClassLength
|
107
417
|
end
|
108
418
|
end
|
109
419
|
end
|
@@ -7,24 +7,37 @@ module ReactOnRails
|
|
7
7
|
module Generators
|
8
8
|
class ReactNoReduxGenerator < Rails::Generators::Base
|
9
9
|
include GeneratorHelper
|
10
|
+
|
10
11
|
Rails::Generators.hide_namespace(namespace)
|
11
12
|
source_root(File.expand_path("templates", __dir__))
|
12
13
|
|
14
|
+
class_option :typescript,
|
15
|
+
type: :boolean,
|
16
|
+
default: false,
|
17
|
+
desc: "Generate TypeScript files"
|
18
|
+
|
13
19
|
def copy_base_files
|
14
20
|
base_js_path = "base/base"
|
15
|
-
|
16
|
-
|
21
|
+
|
22
|
+
# Determine which component files to copy based on TypeScript option
|
23
|
+
component_files = [
|
24
|
+
"app/javascript/src/HelloWorld/ror_components/HelloWorld.client.#{component_extension(options)}",
|
25
|
+
"app/javascript/src/HelloWorld/ror_components/HelloWorld.server.#{component_extension(options)}",
|
26
|
+
"app/javascript/src/HelloWorld/ror_components/HelloWorld.module.css"
|
27
|
+
]
|
28
|
+
|
29
|
+
component_files.each do |file|
|
30
|
+
copy_file("#{base_js_path}/#{file}", file)
|
31
|
+
end
|
17
32
|
end
|
18
33
|
|
19
34
|
def create_appropriate_templates
|
20
35
|
base_path = "base/base"
|
21
36
|
config = {
|
22
|
-
component_name: "HelloWorld"
|
23
|
-
app_relative_path: "../bundles/HelloWorld/components/HelloWorld"
|
37
|
+
component_name: "HelloWorld"
|
24
38
|
}
|
25
39
|
|
26
|
-
template
|
27
|
-
"app/javascript/packs/hello-world-bundle.js", config)
|
40
|
+
# Only create the view template - no manual bundle needed for auto registration
|
28
41
|
template("#{base_path}/app/views/hello_world/index.html.erb.tt",
|
29
42
|
"app/views/hello_world/index.html.erb", config)
|
30
43
|
end
|
@@ -1,51 +1,144 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require "rails/generators"
|
4
|
+
require_relative "generator_helper"
|
5
|
+
require_relative "generator_messages"
|
4
6
|
|
5
7
|
module ReactOnRails
|
6
8
|
module Generators
|
7
9
|
class ReactWithReduxGenerator < Rails::Generators::Base
|
10
|
+
include GeneratorHelper
|
11
|
+
|
8
12
|
Rails::Generators.hide_namespace(namespace)
|
9
13
|
source_root(File.expand_path("templates", __dir__))
|
10
14
|
|
15
|
+
class_option :typescript,
|
16
|
+
type: :boolean,
|
17
|
+
default: false,
|
18
|
+
desc: "Generate TypeScript files",
|
19
|
+
aliases: "-T"
|
20
|
+
|
11
21
|
def create_redux_directories
|
12
|
-
|
13
|
-
|
22
|
+
# Create auto-registration directory structure for Redux
|
23
|
+
empty_directory("app/javascript/src/HelloWorldApp/ror_components")
|
24
|
+
|
25
|
+
# Create Redux support directories within the component directory
|
26
|
+
dirs = %w[actions constants containers reducers store components]
|
27
|
+
dirs.each { |name| empty_directory("app/javascript/src/HelloWorldApp/#{name}") }
|
14
28
|
end
|
15
29
|
|
16
30
|
def copy_base_files
|
17
31
|
base_js_path = "redux/base"
|
18
|
-
|
19
|
-
|
32
|
+
ext = component_extension(options)
|
33
|
+
|
34
|
+
# Copy Redux-connected component to auto-registration structure
|
35
|
+
copy_file("#{base_js_path}/app/javascript/bundles/HelloWorld/startup/HelloWorldApp.client.#{ext}",
|
36
|
+
"app/javascript/src/HelloWorldApp/ror_components/HelloWorldApp.client.#{ext}")
|
37
|
+
copy_file("#{base_js_path}/app/javascript/bundles/HelloWorld/startup/HelloWorldApp.server.#{ext}",
|
38
|
+
"app/javascript/src/HelloWorldApp/ror_components/HelloWorldApp.server.#{ext}")
|
39
|
+
copy_file("#{base_js_path}/app/javascript/bundles/HelloWorld/components/HelloWorld.module.css",
|
40
|
+
"app/javascript/src/HelloWorldApp/components/HelloWorld.module.css")
|
41
|
+
|
42
|
+
# Update import paths in client component
|
43
|
+
ror_client_file = "app/javascript/src/HelloWorldApp/ror_components/HelloWorldApp.client.#{ext}"
|
44
|
+
gsub_file(ror_client_file, "../store/helloWorldStore", "../store/helloWorldStore")
|
45
|
+
gsub_file(ror_client_file, "../containers/HelloWorldContainer",
|
46
|
+
"../containers/HelloWorldContainer")
|
20
47
|
end
|
21
48
|
|
22
49
|
def copy_base_redux_files
|
23
50
|
base_hello_world_path = "redux/base/app/javascript/bundles/HelloWorld"
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
51
|
+
redux_extension = options.typescript? ? "ts" : "js"
|
52
|
+
|
53
|
+
# Copy Redux infrastructure files with appropriate extension
|
54
|
+
%W[actions/helloWorldActionCreators.#{redux_extension}
|
55
|
+
containers/HelloWorldContainer.#{redux_extension}
|
56
|
+
constants/helloWorldConstants.#{redux_extension}
|
57
|
+
reducers/helloWorldReducer.#{redux_extension}
|
58
|
+
store/helloWorldStore.#{redux_extension}
|
59
|
+
components/HelloWorld.#{component_extension(options)}].each do |file|
|
30
60
|
copy_file("#{base_hello_world_path}/#{file}",
|
31
|
-
"app/javascript/
|
61
|
+
"app/javascript/src/HelloWorldApp/#{file}")
|
32
62
|
end
|
33
63
|
end
|
34
64
|
|
35
65
|
def create_appropriate_templates
|
36
66
|
base_path = "base/base"
|
37
|
-
base_js_path = "#{base_path}/app/javascript"
|
38
67
|
config = {
|
39
|
-
component_name: "HelloWorldApp"
|
40
|
-
app_relative_path: "../bundles/HelloWorld/startup/HelloWorldApp"
|
68
|
+
component_name: "HelloWorldApp"
|
41
69
|
}
|
42
70
|
|
43
|
-
|
44
|
-
template("#{base_path}/app/views/hello_world/index.html.erb.tt",
|
71
|
+
# Only create the view template - no manual bundle needed for auto registration
|
72
|
+
template("#{base_path}/app/views/hello_world/index.html.erb.tt",
|
73
|
+
"app/views/hello_world/index.html.erb", config)
|
74
|
+
end
|
75
|
+
|
76
|
+
def add_redux_npm_dependencies
|
77
|
+
# Add Redux dependencies as regular dependencies
|
78
|
+
regular_packages = %w[redux react-redux]
|
79
|
+
|
80
|
+
# Try using GeneratorHelper first (package manager agnostic)
|
81
|
+
success = add_npm_dependencies(regular_packages)
|
82
|
+
|
83
|
+
# Fallback to package manager detection if GeneratorHelper fails
|
84
|
+
return if success
|
85
|
+
|
86
|
+
package_manager = GeneratorMessages.detect_package_manager
|
87
|
+
return unless package_manager
|
88
|
+
|
89
|
+
install_packages_with_fallback(regular_packages, dev: false, package_manager: package_manager)
|
90
|
+
end
|
91
|
+
|
92
|
+
private
|
93
|
+
|
94
|
+
def install_packages_with_fallback(packages, dev:, package_manager:)
|
95
|
+
install_args = build_install_args(package_manager, dev, packages)
|
96
|
+
|
97
|
+
success = system(*install_args)
|
98
|
+
return if success
|
99
|
+
|
100
|
+
install_command = install_args.join(" ")
|
101
|
+
warning = <<~MSG.strip
|
102
|
+
⚠️ Failed to install Redux dependencies automatically.
|
103
|
+
|
104
|
+
Please run manually:
|
105
|
+
#{install_command}
|
106
|
+
MSG
|
107
|
+
GeneratorMessages.add_warning(warning)
|
108
|
+
end
|
109
|
+
|
110
|
+
def build_install_args(package_manager, dev, packages)
|
111
|
+
# Security: Validate package manager to prevent command injection
|
112
|
+
allowed_package_managers = %w[npm yarn pnpm bun].freeze
|
113
|
+
unless allowed_package_managers.include?(package_manager)
|
114
|
+
raise ArgumentError, "Invalid package manager: #{package_manager}"
|
115
|
+
end
|
116
|
+
|
117
|
+
base_commands = {
|
118
|
+
"npm" => %w[npm install],
|
119
|
+
"yarn" => %w[yarn add],
|
120
|
+
"pnpm" => %w[pnpm add],
|
121
|
+
"bun" => %w[bun add]
|
122
|
+
}
|
123
|
+
|
124
|
+
base_args = base_commands[package_manager].dup
|
125
|
+
base_args << dev_flag_for(package_manager) if dev
|
126
|
+
base_args + packages
|
127
|
+
end
|
128
|
+
|
129
|
+
def dev_flag_for(package_manager)
|
130
|
+
case package_manager
|
131
|
+
when "npm", "pnpm" then "--save-dev"
|
132
|
+
when "yarn", "bun" then "--dev"
|
133
|
+
end
|
45
134
|
end
|
46
135
|
|
47
|
-
def
|
48
|
-
|
136
|
+
def add_redux_specific_messages
|
137
|
+
# Override the generic messages with Redux-specific instructions
|
138
|
+
GeneratorMessages.output.clear
|
139
|
+
GeneratorMessages.add_info(
|
140
|
+
GeneratorMessages.helpful_message_after_installation(component_name: "HelloWorldApp", route: "hello_world")
|
141
|
+
)
|
49
142
|
end
|
50
143
|
end
|
51
144
|
end
|
@@ -0,0 +1,8 @@
|
|
1
|
+
# Procfile for development with production assets
|
2
|
+
# Uses production-optimized, precompiled assets with development environment
|
3
|
+
# Uncomment additional processes as needed for your app
|
4
|
+
|
5
|
+
rails: bundle exec rails s -p 3001
|
6
|
+
# sidekiq: bundle exec sidekiq -C config/sidekiq.yml
|
7
|
+
# redis: redis-server
|
8
|
+
# mailcatcher: mailcatcher --foreground
|
@@ -1,4 +1,3 @@
|
|
1
|
-
import PropTypes from 'prop-types';
|
2
1
|
import React, { useState } from 'react';
|
3
2
|
import * as style from './HelloWorld.module.css';
|
4
3
|
|
@@ -19,8 +18,4 @@ const HelloWorld = (props) => {
|
|
19
18
|
);
|
20
19
|
};
|
21
20
|
|
22
|
-
HelloWorld.propTypes = {
|
23
|
-
name: PropTypes.string.isRequired, // this is passed from the Rails view
|
24
|
-
};
|
25
|
-
|
26
21
|
export default HelloWorld;
|