react_on_rails 16.0.0 → 16.0.1.rc.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +124 -77
- data/CLAUDE.md +46 -2
- data/CONTRIBUTING.md +12 -6
- data/Gemfile.development_dependencies +1 -0
- data/Gemfile.lock +3 -1
- data/LICENSE.md +15 -1
- data/README.md +68 -18
- data/bin/lefthook/check-trailing-newlines +38 -0
- data/bin/lefthook/get-changed-files +26 -0
- data/bin/lefthook/prettier-format +26 -0
- data/bin/lefthook/ruby-autofix +26 -0
- data/bin/lefthook/ruby-lint +27 -0
- data/eslint.config.ts +10 -0
- data/knip.ts +20 -9
- data/lib/generators/react_on_rails/USAGE +65 -0
- data/lib/generators/react_on_rails/base_generator.rb +7 -7
- data/lib/generators/react_on_rails/generator_helper.rb +4 -0
- data/lib/generators/react_on_rails/generator_messages.rb +2 -2
- data/lib/generators/react_on_rails/install_generator.rb +115 -7
- data/lib/generators/react_on_rails/react_no_redux_generator.rb +16 -4
- data/lib/generators/react_on_rails/react_with_redux_generator.rb +83 -14
- data/lib/generators/react_on_rails/templates/base/base/app/javascript/src/HelloWorld/ror_components/HelloWorld.client.tsx +25 -0
- data/lib/generators/react_on_rails/templates/base/base/app/javascript/src/HelloWorld/ror_components/HelloWorld.server.tsx +5 -0
- data/lib/generators/react_on_rails/templates/base/base/bin/dev +12 -24
- data/lib/generators/react_on_rails/templates/redux/base/app/javascript/bundles/HelloWorld/actions/helloWorldActionCreators.ts +18 -0
- data/lib/generators/react_on_rails/templates/redux/base/app/javascript/bundles/HelloWorld/components/HelloWorld.tsx +24 -0
- data/lib/generators/react_on_rails/templates/redux/base/app/javascript/bundles/HelloWorld/constants/helloWorldConstants.ts +6 -0
- data/lib/generators/react_on_rails/templates/redux/base/app/javascript/bundles/HelloWorld/containers/HelloWorldContainer.ts +20 -0
- data/lib/generators/react_on_rails/templates/redux/base/app/javascript/bundles/HelloWorld/reducers/helloWorldReducer.ts +22 -0
- data/lib/generators/react_on_rails/templates/redux/base/app/javascript/bundles/HelloWorld/startup/HelloWorldApp.client.tsx +23 -0
- data/lib/generators/react_on_rails/templates/redux/base/app/javascript/bundles/HelloWorld/startup/HelloWorldApp.server.tsx +5 -0
- data/lib/generators/react_on_rails/templates/redux/base/app/javascript/bundles/HelloWorld/store/helloWorldStore.ts +18 -0
- data/lib/react_on_rails/configuration.rb +10 -6
- data/lib/react_on_rails/dev/server_manager.rb +185 -28
- data/lib/react_on_rails/doctor.rb +1149 -0
- data/lib/react_on_rails/helper.rb +9 -78
- data/lib/react_on_rails/pro/NOTICE +21 -0
- data/lib/react_on_rails/pro/helper.rb +122 -0
- data/lib/react_on_rails/pro/utils.rb +53 -0
- data/lib/react_on_rails/react_component/render_options.rb +6 -2
- data/lib/react_on_rails/system_checker.rb +659 -0
- data/lib/react_on_rails/version.rb +1 -1
- data/lib/tasks/doctor.rake +48 -0
- data/lib/tasks/generate_packs.rake +127 -4
- data/package-lock.json +11984 -0
- metadata +26 -6
- data/lib/generators/react_on_rails/bin/dev +0 -46
@@ -0,0 +1,26 @@
|
|
1
|
+
#!/usr/bin/env bash
|
2
|
+
# Auto-fix Ruby files using rake autofix
|
3
|
+
set -euo pipefail
|
4
|
+
|
5
|
+
CONTEXT="${1:-staged}"
|
6
|
+
files="$(bin/lefthook/get-changed-files "$CONTEXT" '\.(rb|rake|ru)$')"
|
7
|
+
|
8
|
+
if [ -z "$files" ]; then
|
9
|
+
echo "✅ No Ruby files to autofix"
|
10
|
+
exit 0
|
11
|
+
fi
|
12
|
+
|
13
|
+
if [ "$CONTEXT" = "all-changed" ]; then
|
14
|
+
echo "🎨 Autofix on all changed Ruby files:"
|
15
|
+
else
|
16
|
+
echo "🎨 Autofix on $CONTEXT Ruby files:"
|
17
|
+
fi
|
18
|
+
printf " %s\n" $files
|
19
|
+
|
20
|
+
bundle exec rake autofix
|
21
|
+
|
22
|
+
# Re-stage files if running on staged or all-changed context
|
23
|
+
if [ "$CONTEXT" = "staged" ] || [ "$CONTEXT" = "all-changed" ]; then
|
24
|
+
echo $files | xargs -r git add
|
25
|
+
echo "✅ Re-staged formatted files"
|
26
|
+
fi
|
@@ -0,0 +1,27 @@
|
|
1
|
+
#!/usr/bin/env bash
|
2
|
+
# Lint Ruby files with RuboCop
|
3
|
+
set -euo pipefail
|
4
|
+
|
5
|
+
CONTEXT="${1:-staged}"
|
6
|
+
files="$(bin/lefthook/get-changed-files "$CONTEXT" '\.(rb|rake|ru)$')"
|
7
|
+
|
8
|
+
if [ -z "$files" ]; then
|
9
|
+
echo "✅ No Ruby files to lint"
|
10
|
+
exit 0
|
11
|
+
fi
|
12
|
+
|
13
|
+
if [ "$CONTEXT" = "all-changed" ]; then
|
14
|
+
echo "🔍 RuboCop on all changed Ruby files:"
|
15
|
+
else
|
16
|
+
echo "🔍 RuboCop on $CONTEXT Ruby files:"
|
17
|
+
fi
|
18
|
+
printf " %s\n" $files
|
19
|
+
|
20
|
+
if ! bundle exec rubocop --force-exclusion --display-cop-names -- $files; then
|
21
|
+
echo ""
|
22
|
+
echo "❌ RuboCop check failed!"
|
23
|
+
echo "💡 Auto-fix: bundle exec rubocop --auto-correct --force-exclusion -- $files"
|
24
|
+
echo "🚫 Skip hook: git commit --no-verify"
|
25
|
+
exit 1
|
26
|
+
fi
|
27
|
+
echo "✅ RuboCop checks passed for Ruby files"
|
data/eslint.config.ts
CHANGED
@@ -44,6 +44,9 @@ const config = tsEslint.config([
|
|
44
44
|
// fixtures
|
45
45
|
'**/fixtures/',
|
46
46
|
'**/.yalc/**/*',
|
47
|
+
// generator templates - exclude TypeScript templates that need tsconfig.json
|
48
|
+
'**/templates/**/*.tsx',
|
49
|
+
'**/templates/**/*.ts',
|
47
50
|
]),
|
48
51
|
{
|
49
52
|
files: ['**/*.[jt]s', '**/*.[jt]sx', '**/*.[cm][jt]s'],
|
@@ -156,6 +159,13 @@ const config = tsEslint.config([
|
|
156
159
|
'react/prop-types': 'off',
|
157
160
|
},
|
158
161
|
},
|
162
|
+
{
|
163
|
+
files: ['spec/dummy/**/*'],
|
164
|
+
rules: {
|
165
|
+
// The dummy app dependencies are managed separately and may not be installed
|
166
|
+
'import/no-unresolved': 'off',
|
167
|
+
},
|
168
|
+
},
|
159
169
|
{
|
160
170
|
files: ['**/*.ts{x,}', '**/*.[cm]ts'],
|
161
171
|
|
data/knip.ts
CHANGED
@@ -6,21 +6,32 @@ const config: KnipConfig = {
|
|
6
6
|
'.': {
|
7
7
|
entry: [
|
8
8
|
'node_package/src/ReactOnRails.node.ts!',
|
9
|
-
'node_package/src/ReactOnRailsRSC.ts!',
|
10
|
-
'node_package/src/registerServerComponent/client.tsx!',
|
11
|
-
'node_package/src/registerServerComponent/server.tsx!',
|
12
|
-
'node_package/src/registerServerComponent/server.rsc.ts!',
|
13
|
-
'node_package/src/wrapServerComponentRenderer/server.tsx!',
|
14
|
-
'node_package/src/wrapServerComponentRenderer/server.rsc.tsx!',
|
15
|
-
'node_package/src/RSCRoute.tsx!',
|
16
|
-
'node_package/src/ServerComponentFetchError.ts!',
|
9
|
+
'node_package/src/pro/ReactOnRailsRSC.ts!',
|
10
|
+
'node_package/src/pro/registerServerComponent/client.tsx!',
|
11
|
+
'node_package/src/pro/registerServerComponent/server.tsx!',
|
12
|
+
'node_package/src/pro/registerServerComponent/server.rsc.ts!',
|
13
|
+
'node_package/src/pro/wrapServerComponentRenderer/server.tsx!',
|
14
|
+
'node_package/src/pro/wrapServerComponentRenderer/server.rsc.tsx!',
|
15
|
+
'node_package/src/pro/RSCRoute.tsx!',
|
16
|
+
'node_package/src/pro/ServerComponentFetchError.ts!',
|
17
|
+
'node_package/src/pro/getReactServerComponent.server.ts!',
|
18
|
+
'node_package/src/pro/transformRSCNodeStream.ts!',
|
19
|
+
'node_package/src/loadJsonFile.ts!',
|
17
20
|
'eslint.config.ts',
|
18
21
|
],
|
19
22
|
project: ['node_package/src/**/*.[jt]s{x,}!', 'node_package/tests/**/*.[jt]s{x,}'],
|
20
23
|
babel: {
|
21
24
|
config: ['node_package/babel.config.js'],
|
22
25
|
},
|
23
|
-
ignore: [
|
26
|
+
ignore: [
|
27
|
+
'node_package/tests/emptyForTesting.js',
|
28
|
+
// Pro features exported for external consumption
|
29
|
+
'node_package/src/pro/streamServerRenderedReactComponent.ts:transformRenderStreamChunksToResultObject',
|
30
|
+
'node_package/src/pro/streamServerRenderedReactComponent.ts:streamServerRenderedComponent',
|
31
|
+
'node_package/src/pro/ServerComponentFetchError.ts:isServerComponentFetchError',
|
32
|
+
'node_package/src/pro/RSCRoute.tsx:RSCRouteProps',
|
33
|
+
'node_package/src/pro/streamServerRenderedReactComponent.ts:StreamingTrackers',
|
34
|
+
],
|
24
35
|
ignoreBinaries: [
|
25
36
|
// Knip fails to detect it's declared in devDependencies
|
26
37
|
'nps',
|
@@ -0,0 +1,65 @@
|
|
1
|
+
Description:
|
2
|
+
The `react_on_rails:doctor` generator diagnoses your React on Rails setup
|
3
|
+
and identifies potential configuration issues. It performs comprehensive
|
4
|
+
checks on your environment, dependencies, and configuration files.
|
5
|
+
|
6
|
+
This command is especially useful for:
|
7
|
+
• Troubleshooting setup issues
|
8
|
+
• Verifying installation after running react_on_rails:install
|
9
|
+
• Ensuring compatibility after upgrades
|
10
|
+
• Getting help with configuration problems
|
11
|
+
|
12
|
+
Example:
|
13
|
+
# Basic diagnosis
|
14
|
+
rails generate react_on_rails:doctor
|
15
|
+
|
16
|
+
# Verbose output showing all checks
|
17
|
+
rails generate react_on_rails:doctor --verbose
|
18
|
+
|
19
|
+
# Show help
|
20
|
+
rails generate react_on_rails:doctor --help
|
21
|
+
|
22
|
+
Checks performed:
|
23
|
+
Environment Prerequisites:
|
24
|
+
• Node.js installation and version compatibility
|
25
|
+
• JavaScript package manager availability (npm, yarn, pnpm, bun)
|
26
|
+
• Git working directory status
|
27
|
+
|
28
|
+
React on Rails Packages:
|
29
|
+
• React on Rails gem installation
|
30
|
+
• react-on-rails NPM package installation
|
31
|
+
• Version synchronization between gem and NPM package
|
32
|
+
• Shakapacker configuration and installation
|
33
|
+
|
34
|
+
Dependencies:
|
35
|
+
• React and React DOM installation
|
36
|
+
• Babel preset configuration
|
37
|
+
• Required development dependencies
|
38
|
+
|
39
|
+
Rails Integration:
|
40
|
+
• React on Rails initializer configuration
|
41
|
+
• Route and controller setup (Hello World example)
|
42
|
+
• View helper integration
|
43
|
+
|
44
|
+
Webpack Configuration:
|
45
|
+
• Webpack config file existence and structure
|
46
|
+
• React on Rails compatibility checks
|
47
|
+
• Environment-specific configuration validation
|
48
|
+
|
49
|
+
Development Environment:
|
50
|
+
• JavaScript bundle files
|
51
|
+
• Procfile.dev for development workflow
|
52
|
+
• .gitignore configuration for generated files
|
53
|
+
|
54
|
+
Options:
|
55
|
+
--verbose, -v: Show detailed output for all checks, including successful ones
|
56
|
+
--fix, -f: Attempt to fix simple issues automatically (planned feature)
|
57
|
+
|
58
|
+
Exit codes:
|
59
|
+
0: All checks passed or only warnings found
|
60
|
+
1: Critical errors found that prevent React on Rails from working
|
61
|
+
|
62
|
+
For more help:
|
63
|
+
• Documentation: https://github.com/shakacode/react_on_rails
|
64
|
+
• Issues: https://github.com/shakacode/react_on_rails/issues
|
65
|
+
• Discord: https://discord.gg/reactrails
|
@@ -105,13 +105,13 @@ module ReactOnRails
|
|
105
105
|
def install_js_dependencies
|
106
106
|
# Detect which package manager to use
|
107
107
|
success = if File.exist?(File.join(destination_root, "yarn.lock"))
|
108
|
-
|
108
|
+
system("yarn", "install")
|
109
109
|
elsif File.exist?(File.join(destination_root, "pnpm-lock.yaml"))
|
110
|
-
|
110
|
+
system("pnpm", "install")
|
111
111
|
elsif File.exist?(File.join(destination_root, "package-lock.json")) ||
|
112
112
|
File.exist?(File.join(destination_root, "package.json"))
|
113
113
|
# Use npm for package-lock.json or as default fallback
|
114
|
-
|
114
|
+
system("npm", "install")
|
115
115
|
else
|
116
116
|
true # No package manager detected, skip
|
117
117
|
end
|
@@ -173,7 +173,7 @@ module ReactOnRails
|
|
173
173
|
return if add_npm_dependencies(react_on_rails_pkg)
|
174
174
|
|
175
175
|
puts "Using direct npm commands as fallback"
|
176
|
-
success =
|
176
|
+
success = system("npm", "install", *react_on_rails_pkg)
|
177
177
|
handle_npm_failure("react-on-rails package", react_on_rails_pkg) unless success
|
178
178
|
end
|
179
179
|
|
@@ -189,7 +189,7 @@ module ReactOnRails
|
|
189
189
|
]
|
190
190
|
return if add_npm_dependencies(react_deps)
|
191
191
|
|
192
|
-
success =
|
192
|
+
success = system("npm", "install", *react_deps)
|
193
193
|
handle_npm_failure("React dependencies", react_deps) unless success
|
194
194
|
end
|
195
195
|
|
@@ -203,7 +203,7 @@ module ReactOnRails
|
|
203
203
|
]
|
204
204
|
return if add_npm_dependencies(css_deps)
|
205
205
|
|
206
|
-
success =
|
206
|
+
success = system("npm", "install", *css_deps)
|
207
207
|
handle_npm_failure("CSS dependencies", css_deps) unless success
|
208
208
|
end
|
209
209
|
|
@@ -215,7 +215,7 @@ module ReactOnRails
|
|
215
215
|
]
|
216
216
|
return if add_npm_dependencies(dev_deps, dev: true)
|
217
217
|
|
218
|
-
success =
|
218
|
+
success = system("npm", "install", "--save-dev", *dev_deps)
|
219
219
|
handle_npm_failure("development dependencies", dev_deps, dev: true) unless success
|
220
220
|
end
|
221
221
|
|
@@ -38,7 +38,7 @@ module GeneratorMessages
|
|
38
38
|
@output = []
|
39
39
|
end
|
40
40
|
|
41
|
-
def helpful_message_after_installation(component_name: "HelloWorld")
|
41
|
+
def helpful_message_after_installation(component_name: "HelloWorld", route: "hello_world")
|
42
42
|
process_manager_section = build_process_manager_section
|
43
43
|
testing_section = build_testing_section
|
44
44
|
package_manager = detect_package_manager
|
@@ -62,7 +62,7 @@ module GeneratorMessages
|
|
62
62
|
./bin/dev prod # Production-like mode for testing
|
63
63
|
./bin/dev help # See all available options
|
64
64
|
|
65
|
-
3. Visit: #{Rainbow('http://localhost:3000
|
65
|
+
3. Visit: #{Rainbow(route ? "http://localhost:3000/#{route}" : 'http://localhost:3000').cyan.underline}
|
66
66
|
✨ KEY FEATURES:
|
67
67
|
─────────────────────────────────────────────────────────────────────────
|
68
68
|
• Auto-registration enabled - Your layout only needs:
|
@@ -1,6 +1,7 @@
|
|
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
|
|
@@ -20,6 +21,13 @@ module ReactOnRails
|
|
20
21
|
desc: "Install Redux package and Redux version of Hello World Example. Default: false",
|
21
22
|
aliases: "-R"
|
22
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
|
+
|
23
31
|
# --ignore-warnings
|
24
32
|
class_option :ignore_warnings,
|
25
33
|
type: :boolean,
|
@@ -58,11 +66,16 @@ module ReactOnRails
|
|
58
66
|
|
59
67
|
def invoke_generators
|
60
68
|
ensure_shakapacker_installed
|
61
|
-
|
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? }
|
62
75
|
if options.redux?
|
63
|
-
invoke "react_on_rails:react_with_redux"
|
76
|
+
invoke "react_on_rails:react_with_redux", [], { typescript: options.typescript? }
|
64
77
|
else
|
65
|
-
invoke "react_on_rails:react_no_redux"
|
78
|
+
invoke "react_on_rails:react_no_redux", [], { typescript: options.typescript? }
|
66
79
|
end
|
67
80
|
end
|
68
81
|
|
@@ -136,11 +149,13 @@ module ReactOnRails
|
|
136
149
|
end
|
137
150
|
|
138
151
|
def add_bin_scripts
|
139
|
-
|
152
|
+
# Copy bin scripts from templates
|
153
|
+
template_bin_path = "#{__dir__}/templates/base/base/bin"
|
154
|
+
directory template_bin_path, "bin"
|
140
155
|
|
141
156
|
# Make these and only these files executable
|
142
157
|
files_to_copy = []
|
143
|
-
Dir.chdir(
|
158
|
+
Dir.chdir(template_bin_path) do
|
144
159
|
files_to_copy.concat(Dir.glob("*"))
|
145
160
|
end
|
146
161
|
files_to_become_executable = files_to_copy.map { |filename| "bin/#{filename}" }
|
@@ -149,7 +164,14 @@ module ReactOnRails
|
|
149
164
|
end
|
150
165
|
|
151
166
|
def add_post_install_message
|
152
|
-
|
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
|
+
))
|
153
175
|
end
|
154
176
|
|
155
177
|
def shakapacker_loaded_in_process?(gem_name)
|
@@ -302,10 +324,96 @@ module ReactOnRails
|
|
302
324
|
false
|
303
325
|
end
|
304
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
|
+
|
305
413
|
# Removed: Shakapacker auto-installation logic (now explicit dependency)
|
306
414
|
|
307
415
|
# Removed: Shakapacker 8+ is now required as explicit dependency
|
416
|
+
# rubocop:enable Metrics/ClassLength
|
308
417
|
end
|
309
|
-
# rubocop:enable Metrics/ClassLength
|
310
418
|
end
|
311
419
|
end
|
@@ -11,12 +11,24 @@ module ReactOnRails
|
|
11
11
|
Rails::Generators.hide_namespace(namespace)
|
12
12
|
source_root(File.expand_path("templates", __dir__))
|
13
13
|
|
14
|
+
class_option :typescript,
|
15
|
+
type: :boolean,
|
16
|
+
default: false,
|
17
|
+
desc: "Generate TypeScript files"
|
18
|
+
|
14
19
|
def copy_base_files
|
15
20
|
base_js_path = "base/base"
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
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
|
20
32
|
end
|
21
33
|
|
22
34
|
def create_appropriate_templates
|
@@ -1,13 +1,23 @@
|
|
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
22
|
# Create auto-registration directory structure for Redux
|
13
23
|
empty_directory("app/javascript/src/HelloWorldApp/ror_components")
|
@@ -19,17 +29,18 @@ module ReactOnRails
|
|
19
29
|
|
20
30
|
def copy_base_files
|
21
31
|
base_js_path = "redux/base"
|
32
|
+
ext = component_extension(options)
|
22
33
|
|
23
34
|
# Copy Redux-connected component to auto-registration structure
|
24
|
-
copy_file("#{base_js_path}/app/javascript/bundles/HelloWorld/startup/HelloWorldApp.client
|
25
|
-
"app/javascript/src/HelloWorldApp/ror_components/HelloWorldApp.client
|
26
|
-
copy_file("#{base_js_path}/app/javascript/bundles/HelloWorld/startup/HelloWorldApp.server
|
27
|
-
"app/javascript/src/HelloWorldApp/ror_components/HelloWorldApp.server
|
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}")
|
28
39
|
copy_file("#{base_js_path}/app/javascript/bundles/HelloWorld/components/HelloWorld.module.css",
|
29
40
|
"app/javascript/src/HelloWorldApp/components/HelloWorld.module.css")
|
30
41
|
|
31
42
|
# Update import paths in client component
|
32
|
-
ror_client_file = "app/javascript/src/HelloWorldApp/ror_components/HelloWorldApp.client
|
43
|
+
ror_client_file = "app/javascript/src/HelloWorldApp/ror_components/HelloWorldApp.client.#{ext}"
|
33
44
|
gsub_file(ror_client_file, "../store/helloWorldStore", "../store/helloWorldStore")
|
34
45
|
gsub_file(ror_client_file, "../containers/HelloWorldContainer",
|
35
46
|
"../containers/HelloWorldContainer")
|
@@ -37,12 +48,15 @@ module ReactOnRails
|
|
37
48
|
|
38
49
|
def copy_base_redux_files
|
39
50
|
base_hello_world_path = "redux/base/app/javascript/bundles/HelloWorld"
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
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|
|
46
60
|
copy_file("#{base_hello_world_path}/#{file}",
|
47
61
|
"app/javascript/src/HelloWorldApp/#{file}")
|
48
62
|
end
|
@@ -60,15 +74,70 @@ module ReactOnRails
|
|
60
74
|
end
|
61
75
|
|
62
76
|
def add_redux_npm_dependencies
|
63
|
-
|
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
|
64
134
|
end
|
65
135
|
|
66
136
|
def add_redux_specific_messages
|
67
137
|
# Override the generic messages with Redux-specific instructions
|
68
|
-
require_relative "generator_messages"
|
69
138
|
GeneratorMessages.output.clear
|
70
139
|
GeneratorMessages.add_info(
|
71
|
-
GeneratorMessages.helpful_message_after_installation(component_name: "HelloWorldApp")
|
140
|
+
GeneratorMessages.helpful_message_after_installation(component_name: "HelloWorldApp", route: "hello_world")
|
72
141
|
)
|
73
142
|
end
|
74
143
|
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
import React, { useState } from 'react';
|
2
|
+
import * as style from './HelloWorld.module.css';
|
3
|
+
|
4
|
+
interface HelloWorldProps {
|
5
|
+
name: string;
|
6
|
+
}
|
7
|
+
|
8
|
+
const HelloWorld: React.FC<HelloWorldProps> = (props) => {
|
9
|
+
const [name, setName] = useState(props.name);
|
10
|
+
|
11
|
+
return (
|
12
|
+
<div>
|
13
|
+
<h3>Hello, {name}!</h3>
|
14
|
+
<hr />
|
15
|
+
<form>
|
16
|
+
<label className={style.bright} htmlFor="name">
|
17
|
+
Say hello to:
|
18
|
+
<input id="name" type="text" value={name} onChange={(e) => setName(e.target.value)} />
|
19
|
+
</label>
|
20
|
+
</form>
|
21
|
+
</div>
|
22
|
+
);
|
23
|
+
};
|
24
|
+
|
25
|
+
export default HelloWorld;
|