react_on_rails 14.2.1 → 16.1.1
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/AI_AGENT_INSTRUCTIONS.md +63 -0
- data/CHANGELOG.md +564 -85
- data/CLAUDE.md +135 -0
- data/CODING_AGENTS.md +313 -0
- data/CONTRIBUTING.md +448 -37
- data/Gemfile.development_dependencies +6 -1
- data/Gemfile.lock +13 -4
- data/KUDOS.md +22 -1
- data/LICENSE.md +30 -4
- data/LICENSES/README.md +14 -0
- data/NEWS.md +48 -48
- data/PROJECTS.md +45 -40
- data/REACT-ON-RAILS-PRO-LICENSE.md +129 -0
- data/README.md +113 -42
- data/SUMMARY.md +62 -52
- data/TODO.md +135 -0
- 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 +232 -0
- data/knip.ts +40 -6
- data/lib/generators/USAGE +4 -5
- data/lib/generators/react_on_rails/USAGE +65 -0
- data/lib/generators/react_on_rails/base_generator.rb +276 -62
- 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 +474 -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 +110 -18
- data/lib/generators/react_on_rails/templates/.eslintrc +1 -1
- 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/bundles/HelloWorld/components/HelloWorld.module.css +2 -2
- data/lib/generators/react_on_rails/templates/base/base/app/javascript/bundles/HelloWorld/components/HelloWorldServer.js +1 -1
- 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 +14 -5
- 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 +6 -10
- 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/serverWebpackConfig.js.tt +3 -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.js +1 -1
- 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 +141 -57
- data/lib/react_on_rails/controller.rb +6 -2
- 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 +176 -74
- data/lib/react_on_rails/json_parse_error.rb +6 -1
- data/lib/react_on_rails/packer_utils.rb +61 -71
- data/lib/react_on_rails/packs_generator.rb +221 -19
- data/lib/react_on_rails/prerender_error.rb +4 -0
- 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 +38 -6
- 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 +12 -5
- data/lib/react_on_rails/system_checker.rb +659 -0
- data/lib/react_on_rails/test_helper/webpack_assets_compiler.rb +1 -1
- data/lib/react_on_rails/test_helper/webpack_assets_status_checker.rb +6 -4
- data/lib/react_on_rails/test_helper.rb +2 -3
- data/lib/react_on_rails/utils.rb +139 -43
- data/lib/react_on_rails/version.rb +1 -1
- data/lib/react_on_rails/version_checker.rb +14 -20
- data/lib/react_on_rails/version_syntax_converter.rb +1 -1
- data/lib/react_on_rails.rb +1 -0
- data/lib/tasks/assets.rake +1 -1
- data/lib/tasks/doctor.rake +48 -0
- data/lib/tasks/generate_packs.rake +158 -1
- data/react_on_rails.gemspec +1 -0
- data/tsconfig.eslint.json +6 -0
- data/tsconfig.json +5 -3
- metadata +63 -14
- 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
@@ -0,0 +1,61 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ReactOnRails
|
4
|
+
module Dev
|
5
|
+
class ProcessManager
|
6
|
+
class << self
|
7
|
+
def installed?(process)
|
8
|
+
IO.popen([process, "-v"], &:close)
|
9
|
+
true
|
10
|
+
rescue Errno::ENOENT
|
11
|
+
false
|
12
|
+
end
|
13
|
+
|
14
|
+
def ensure_procfile(procfile)
|
15
|
+
return if File.exist?(procfile)
|
16
|
+
|
17
|
+
warn <<~MSG
|
18
|
+
ERROR:
|
19
|
+
Please ensure `#{procfile}` exists in your project!
|
20
|
+
MSG
|
21
|
+
exit 1
|
22
|
+
end
|
23
|
+
|
24
|
+
def run_with_process_manager(procfile)
|
25
|
+
# Validate procfile path for security
|
26
|
+
unless valid_procfile_path?(procfile)
|
27
|
+
warn "ERROR: Invalid procfile path: #{procfile}"
|
28
|
+
exit 1
|
29
|
+
end
|
30
|
+
|
31
|
+
# Clean up stale files before starting
|
32
|
+
FileManager.cleanup_stale_files
|
33
|
+
|
34
|
+
if installed?("overmind")
|
35
|
+
system("overmind", "start", "-f", procfile)
|
36
|
+
elsif installed?("foreman")
|
37
|
+
system("foreman", "start", "-f", procfile)
|
38
|
+
else
|
39
|
+
warn <<~MSG
|
40
|
+
NOTICE:
|
41
|
+
For this script to run, you need either 'overmind' or 'foreman' installed on your machine. Please try this script after installing one of them.
|
42
|
+
MSG
|
43
|
+
exit 1
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
private
|
48
|
+
|
49
|
+
def valid_procfile_path?(procfile)
|
50
|
+
# Reject paths with shell metacharacters
|
51
|
+
return false if procfile.match?(/[;&|`$(){}\[\]<>]/)
|
52
|
+
|
53
|
+
# Ensure it's a readable file
|
54
|
+
File.readable?(procfile)
|
55
|
+
rescue StandardError
|
56
|
+
false
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,487 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "English"
|
4
|
+
require "open3"
|
5
|
+
require "rainbow"
|
6
|
+
|
7
|
+
module ReactOnRails
|
8
|
+
module Dev
|
9
|
+
class ServerManager
|
10
|
+
class << self
|
11
|
+
def start(mode = :development, procfile = nil, verbose: false, route: nil, rails_env: nil)
|
12
|
+
case mode
|
13
|
+
when :production_like
|
14
|
+
run_production_like(_verbose: verbose, route: route, rails_env: rails_env)
|
15
|
+
when :static
|
16
|
+
procfile ||= "Procfile.dev-static-assets"
|
17
|
+
run_static_development(procfile, verbose: verbose, route: route)
|
18
|
+
when :development, :hmr
|
19
|
+
procfile ||= "Procfile.dev"
|
20
|
+
run_development(procfile, verbose: verbose, route: route)
|
21
|
+
else
|
22
|
+
raise ArgumentError, "Unknown mode: #{mode}"
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def kill_processes
|
27
|
+
puts "🔪 Killing all development processes..."
|
28
|
+
puts ""
|
29
|
+
|
30
|
+
killed_any = kill_running_processes || cleanup_socket_files
|
31
|
+
|
32
|
+
print_kill_summary(killed_any)
|
33
|
+
end
|
34
|
+
|
35
|
+
def development_processes
|
36
|
+
{
|
37
|
+
"rails" => "Rails server",
|
38
|
+
"node.*react[-_]on[-_]rails" => "React on Rails Node processes",
|
39
|
+
"overmind" => "Overmind process manager",
|
40
|
+
"foreman" => "Foreman process manager",
|
41
|
+
"ruby.*puma" => "Puma server",
|
42
|
+
"webpack-dev-server" => "Webpack dev server",
|
43
|
+
"bin/shakapacker-dev-server" => "Shakapacker dev server"
|
44
|
+
}
|
45
|
+
end
|
46
|
+
|
47
|
+
def kill_running_processes
|
48
|
+
killed_any = false
|
49
|
+
|
50
|
+
development_processes.each do |pattern, description|
|
51
|
+
pids = find_process_pids(pattern)
|
52
|
+
next unless pids.any?
|
53
|
+
|
54
|
+
puts " ☠️ Killing #{description} (PIDs: #{pids.join(', ')})"
|
55
|
+
terminate_processes(pids)
|
56
|
+
killed_any = true
|
57
|
+
end
|
58
|
+
|
59
|
+
killed_any
|
60
|
+
end
|
61
|
+
|
62
|
+
def find_process_pids(pattern)
|
63
|
+
stdout, _status = Open3.capture2("pgrep", "-f", pattern, err: File::NULL)
|
64
|
+
stdout.split("\n").map(&:to_i).reject { |pid| pid == Process.pid }
|
65
|
+
rescue Errno::ENOENT
|
66
|
+
# pgrep command not found
|
67
|
+
[]
|
68
|
+
end
|
69
|
+
|
70
|
+
def terminate_processes(pids)
|
71
|
+
pids.each do |pid|
|
72
|
+
Process.kill("TERM", pid)
|
73
|
+
rescue StandardError
|
74
|
+
nil
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
def cleanup_socket_files
|
79
|
+
files = [".overmind.sock", "tmp/sockets/overmind.sock", "tmp/pids/server.pid"]
|
80
|
+
killed_any = false
|
81
|
+
|
82
|
+
files.each do |file|
|
83
|
+
next unless File.exist?(file)
|
84
|
+
|
85
|
+
puts " 🧹 Removing #{file}"
|
86
|
+
File.delete(file)
|
87
|
+
killed_any = true
|
88
|
+
rescue StandardError
|
89
|
+
nil
|
90
|
+
end
|
91
|
+
|
92
|
+
killed_any
|
93
|
+
end
|
94
|
+
|
95
|
+
def print_kill_summary(killed_any)
|
96
|
+
if killed_any
|
97
|
+
puts ""
|
98
|
+
puts "✅ All processes terminated and sockets cleaned"
|
99
|
+
puts "💡 You can now run 'bin/dev' for a clean start"
|
100
|
+
else
|
101
|
+
puts " ℹ️ No development processes found running"
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
def show_help
|
106
|
+
puts help_usage
|
107
|
+
puts ""
|
108
|
+
puts help_commands
|
109
|
+
puts ""
|
110
|
+
puts help_options
|
111
|
+
puts ""
|
112
|
+
puts help_customization
|
113
|
+
puts ""
|
114
|
+
puts help_mode_details
|
115
|
+
puts ""
|
116
|
+
puts help_troubleshooting
|
117
|
+
end
|
118
|
+
|
119
|
+
def run_from_command_line(args = ARGV)
|
120
|
+
require "optparse"
|
121
|
+
|
122
|
+
options = { route: nil, rails_env: nil }
|
123
|
+
|
124
|
+
OptionParser.new do |opts|
|
125
|
+
opts.banner = "Usage: dev [command] [options]"
|
126
|
+
|
127
|
+
opts.on("--route ROUTE", "Specify the route to display in URLs (default: root)") do |route|
|
128
|
+
options[:route] = route
|
129
|
+
end
|
130
|
+
|
131
|
+
opts.on("--rails-env ENV", "Override RAILS_ENV for assets:precompile step only (prod mode only)") do |env|
|
132
|
+
options[:rails_env] = env
|
133
|
+
end
|
134
|
+
|
135
|
+
opts.on("-h", "--help", "Prints this help") do
|
136
|
+
show_help
|
137
|
+
exit
|
138
|
+
end
|
139
|
+
end.parse!(args)
|
140
|
+
|
141
|
+
# Get the command (anything that's not parsed as an option)
|
142
|
+
command = args[0]
|
143
|
+
|
144
|
+
# Main execution
|
145
|
+
case command
|
146
|
+
when "production-assets", "prod"
|
147
|
+
start(:production_like, nil, verbose: false, route: options[:route], rails_env: options[:rails_env])
|
148
|
+
when "static"
|
149
|
+
start(:static, "Procfile.dev-static-assets", verbose: false, route: options[:route])
|
150
|
+
when "kill"
|
151
|
+
kill_processes
|
152
|
+
when "help", "--help", "-h"
|
153
|
+
show_help
|
154
|
+
when "hmr", nil
|
155
|
+
start(:development, "Procfile.dev", verbose: false, route: options[:route])
|
156
|
+
else
|
157
|
+
puts "Unknown argument: #{command}"
|
158
|
+
puts "Run 'dev help' for usage information"
|
159
|
+
exit 1
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
private
|
164
|
+
|
165
|
+
def help_usage
|
166
|
+
Rainbow("📋 Usage: bin/dev [command] [options]").bold
|
167
|
+
end
|
168
|
+
|
169
|
+
# rubocop:disable Metrics/AbcSize
|
170
|
+
def help_commands
|
171
|
+
<<~COMMANDS
|
172
|
+
#{Rainbow('🚀 COMMANDS:').cyan.bold}
|
173
|
+
#{Rainbow('(none) / hmr').green.bold} #{Rainbow('Start development server with HMR (default)').white}
|
174
|
+
#{Rainbow('→ Uses:').yellow} Procfile.dev
|
175
|
+
|
176
|
+
#{Rainbow('static').green.bold} #{Rainbow('Start development server with static assets (no HMR, no FOUC)').white}
|
177
|
+
#{Rainbow('→ Uses:').yellow} Procfile.dev-static-assets
|
178
|
+
|
179
|
+
#{Rainbow('production-assets').green.bold} #{Rainbow('Start with production-optimized assets (no HMR)').white}
|
180
|
+
#{Rainbow('prod').green.bold} #{Rainbow('Alias for production-assets').white}
|
181
|
+
#{Rainbow('→ Uses:').yellow} Procfile.dev-prod-assets
|
182
|
+
|
183
|
+
#{Rainbow('kill').red.bold} #{Rainbow('Kill all development processes for a clean start').white}
|
184
|
+
#{Rainbow('help').blue.bold} #{Rainbow('Show this help message').white}
|
185
|
+
COMMANDS
|
186
|
+
end
|
187
|
+
# rubocop:enable Metrics/AbcSize
|
188
|
+
|
189
|
+
# rubocop:disable Metrics/AbcSize
|
190
|
+
def help_options
|
191
|
+
<<~OPTIONS
|
192
|
+
#{Rainbow('⚙️ OPTIONS:').cyan.bold}
|
193
|
+
#{Rainbow('--route ROUTE').green.bold} #{Rainbow('Specify route to display in URLs (default: root)').white}
|
194
|
+
#{Rainbow('--rails-env ENV').green.bold} #{Rainbow('Override RAILS_ENV for assets:precompile step only (prod mode only)').white}
|
195
|
+
#{Rainbow('--verbose, -v').green.bold} #{Rainbow('Enable verbose output for pack generation').white}
|
196
|
+
|
197
|
+
#{Rainbow('📝 EXAMPLES:').cyan.bold}
|
198
|
+
#{Rainbow('bin/dev prod').green.bold} #{Rainbow('# NODE_ENV=production, RAILS_ENV=development').white}
|
199
|
+
#{Rainbow('bin/dev prod --rails-env=production').green.bold} #{Rainbow('# NODE_ENV=production, RAILS_ENV=production').white}
|
200
|
+
#{Rainbow('bin/dev prod --route=dashboard').green.bold} #{Rainbow('# Custom route in URLs').white}
|
201
|
+
OPTIONS
|
202
|
+
end
|
203
|
+
# rubocop:enable Metrics/AbcSize
|
204
|
+
|
205
|
+
def help_customization
|
206
|
+
<<~CUSTOMIZATION
|
207
|
+
#{Rainbow('🔧 CUSTOMIZATION:').cyan.bold}
|
208
|
+
Each mode uses a specific Procfile that you can customize for your application:
|
209
|
+
|
210
|
+
#{Rainbow('•').yellow} #{Rainbow('Procfile.dev').green.bold} - HMR development with webpack-dev-server
|
211
|
+
#{Rainbow('•').yellow} #{Rainbow('Procfile.dev-static-assets').green.bold} - Static development with webpack --watch
|
212
|
+
#{Rainbow('•').yellow} #{Rainbow('Procfile.dev-prod-assets').green.bold} - Production-optimized assets (port 3001)
|
213
|
+
|
214
|
+
#{Rainbow('Edit these files to customize the development environment for your needs.').white}
|
215
|
+
CUSTOMIZATION
|
216
|
+
end
|
217
|
+
|
218
|
+
# rubocop:disable Metrics/AbcSize
|
219
|
+
def help_mode_details
|
220
|
+
<<~MODES
|
221
|
+
#{Rainbow('🔥 HMR Development mode (default)').cyan.bold} - #{Rainbow('Procfile.dev').green}:
|
222
|
+
#{Rainbow('•').yellow} #{Rainbow('Hot Module Replacement (HMR) enabled').white}
|
223
|
+
#{Rainbow('•').yellow} #{Rainbow('React on Rails pack generation before Procfile start').white}
|
224
|
+
#{Rainbow('•').yellow} #{Rainbow('Webpack dev server for fast recompilation').white}
|
225
|
+
#{Rainbow('•').yellow} #{Rainbow('Source maps for debugging').white}
|
226
|
+
#{Rainbow('•').yellow} #{Rainbow('May have Flash of Unstyled Content (FOUC)').white}
|
227
|
+
#{Rainbow('•').yellow} #{Rainbow('Fast recompilation').white}
|
228
|
+
#{Rainbow('•').yellow} #{Rainbow('Access at:').white} #{Rainbow('http://localhost:3000/<route>').cyan.underline}
|
229
|
+
|
230
|
+
#{Rainbow('📦 Static development mode').cyan.bold} - #{Rainbow('Procfile.dev-static-assets').green}:
|
231
|
+
#{Rainbow('•').yellow} #{Rainbow('No HMR (static assets with auto-recompilation)').white}
|
232
|
+
#{Rainbow('•').yellow} #{Rainbow('React on Rails pack generation before Procfile start').white}
|
233
|
+
#{Rainbow('•').yellow} #{Rainbow('Webpack watch mode for auto-recompilation').white}
|
234
|
+
#{Rainbow('•').yellow} #{Rainbow('CSS extracted to separate files (no FOUC)').white}
|
235
|
+
#{Rainbow('•').yellow} #{Rainbow('Development environment (faster builds than production)').white}
|
236
|
+
#{Rainbow('•').yellow} #{Rainbow('Source maps for debugging').white}
|
237
|
+
#{Rainbow('•').yellow} #{Rainbow('Access at:').white} #{Rainbow('http://localhost:3000/<route>').cyan.underline}
|
238
|
+
|
239
|
+
#{Rainbow('🏭 Production-assets mode').cyan.bold} - #{Rainbow('Procfile.dev-prod-assets').green}:
|
240
|
+
#{Rainbow('•').yellow} #{Rainbow('React on Rails pack generation before Procfile start').white}
|
241
|
+
#{Rainbow('•').yellow} #{Rainbow('Asset precompilation with NODE_ENV=production (webpack optimizations)').white}
|
242
|
+
#{Rainbow('•').yellow} #{Rainbow('RAILS_ENV=development by default for assets:precompile (avoids credentials)').white}
|
243
|
+
#{Rainbow('•').yellow} #{Rainbow('Use --rails-env=production for assets:precompile only (not server processes)').white}
|
244
|
+
#{Rainbow('•').yellow} #{Rainbow('Server processes controlled by Procfile.dev-prod-assets environment').white}
|
245
|
+
#{Rainbow('•').yellow} #{Rainbow('Optimized, minified bundles with CSS extraction').white}
|
246
|
+
#{Rainbow('•').yellow} #{Rainbow('No HMR (static assets)').white}
|
247
|
+
#{Rainbow('•').yellow} #{Rainbow('Access at:').white} #{Rainbow('http://localhost:3001/<route>').cyan.underline}
|
248
|
+
MODES
|
249
|
+
end
|
250
|
+
# rubocop:enable Metrics/AbcSize
|
251
|
+
|
252
|
+
# rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
|
253
|
+
def run_production_like(_verbose: false, route: nil, rails_env: nil)
|
254
|
+
procfile = "Procfile.dev-prod-assets"
|
255
|
+
|
256
|
+
print_procfile_info(procfile, route: route)
|
257
|
+
print_server_info(
|
258
|
+
"🏭 Starting production-like development server...",
|
259
|
+
[
|
260
|
+
"Generating React on Rails packs",
|
261
|
+
"Precompiling assets with production optimizations",
|
262
|
+
"Running Rails server on port 3001",
|
263
|
+
"No HMR (Hot Module Replacement)",
|
264
|
+
"CSS extracted to separate files (no FOUC)"
|
265
|
+
],
|
266
|
+
3001,
|
267
|
+
route: route
|
268
|
+
)
|
269
|
+
|
270
|
+
# Precompile assets with production webpack optimizations (includes pack generation automatically)
|
271
|
+
env = { "NODE_ENV" => "production" }
|
272
|
+
|
273
|
+
# Validate and sanitize rails_env to prevent shell injection
|
274
|
+
if rails_env
|
275
|
+
unless rails_env.match?(/\A[a-z0-9_]+\z/i)
|
276
|
+
puts "❌ Invalid rails_env: '#{rails_env}'. Must contain only letters, numbers, and underscores."
|
277
|
+
exit 1
|
278
|
+
end
|
279
|
+
env["RAILS_ENV"] = rails_env
|
280
|
+
end
|
281
|
+
|
282
|
+
argv = ["bundle", "exec", "rails", "assets:precompile"]
|
283
|
+
|
284
|
+
puts "🔨 Precompiling assets with production webpack optimizations..."
|
285
|
+
puts ""
|
286
|
+
|
287
|
+
puts Rainbow("ℹ️ Asset Precompilation Environment:").blue
|
288
|
+
puts " • NODE_ENV=production → Webpack optimizations (minification, compression)"
|
289
|
+
if rails_env
|
290
|
+
puts " • RAILS_ENV=#{rails_env} → Custom Rails environment for assets:precompile only"
|
291
|
+
puts " • Note: RAILS_ENV=production requires credentials, database setup, etc."
|
292
|
+
puts " • Server processes will use environment from Procfile.dev-prod-assets"
|
293
|
+
else
|
294
|
+
puts " • RAILS_ENV=development → Simpler Rails setup (no credentials needed)"
|
295
|
+
puts " • Use --rails-env=production for assets:precompile step only"
|
296
|
+
puts " • Server processes will use environment from Procfile.dev-prod-assets"
|
297
|
+
puts " • Gets production webpack bundles without production Rails complexity"
|
298
|
+
end
|
299
|
+
puts ""
|
300
|
+
|
301
|
+
env_display = env.map { |k, v| "#{k}=#{v}" }.join(" ")
|
302
|
+
puts "#{Rainbow('💻 Running:').blue} #{env_display} #{argv.join(' ')}"
|
303
|
+
puts ""
|
304
|
+
|
305
|
+
# Capture both stdout and stderr
|
306
|
+
require "open3"
|
307
|
+
stdout, stderr, status = Open3.capture3(env, *argv)
|
308
|
+
|
309
|
+
if status.success?
|
310
|
+
puts "✅ Assets precompiled successfully"
|
311
|
+
ProcessManager.ensure_procfile(procfile)
|
312
|
+
ProcessManager.run_with_process_manager(procfile)
|
313
|
+
else
|
314
|
+
puts "❌ Asset precompilation failed"
|
315
|
+
puts ""
|
316
|
+
|
317
|
+
# Combine and display all output
|
318
|
+
all_output = []
|
319
|
+
all_output << stdout unless stdout.empty?
|
320
|
+
all_output << stderr unless stderr.empty?
|
321
|
+
|
322
|
+
unless all_output.empty?
|
323
|
+
puts Rainbow("📋 Full Command Output:").red.bold
|
324
|
+
puts Rainbow("─" * 60).red
|
325
|
+
all_output.each { |output| puts output }
|
326
|
+
puts Rainbow("─" * 60).red
|
327
|
+
puts ""
|
328
|
+
end
|
329
|
+
|
330
|
+
puts Rainbow("🛠️ To debug this issue:").yellow.bold
|
331
|
+
command_display = "#{env_display} #{argv.join(' ')}"
|
332
|
+
puts "#{Rainbow('1.').cyan} #{Rainbow('Run the command separately to see detailed output:').white}"
|
333
|
+
puts " #{Rainbow(command_display).cyan}"
|
334
|
+
puts ""
|
335
|
+
puts "#{Rainbow('2.').cyan} #{Rainbow('Add --trace for full stack trace:').white}"
|
336
|
+
puts " #{Rainbow("#{command_display} --trace").cyan}"
|
337
|
+
puts ""
|
338
|
+
puts "#{Rainbow('3.').cyan} #{Rainbow('Or try with development webpack (faster, less optimized):').white}"
|
339
|
+
puts " #{Rainbow('NODE_ENV=development bundle exec rails assets:precompile').cyan}"
|
340
|
+
puts ""
|
341
|
+
|
342
|
+
puts Rainbow("💡 Common fixes:").yellow.bold
|
343
|
+
|
344
|
+
# Provide specific guidance based on error content
|
345
|
+
error_content = "#{stderr} #{stdout}".downcase
|
346
|
+
|
347
|
+
if error_content.include?("secret_key_base")
|
348
|
+
puts "#{Rainbow('•').yellow} #{Rainbow('Missing secret_key_base:').white.bold} " \
|
349
|
+
"Run #{Rainbow('bin/rails credentials:edit').cyan}"
|
350
|
+
end
|
351
|
+
|
352
|
+
if error_content.include?("database") || error_content.include?("relation") ||
|
353
|
+
error_content.include?("table")
|
354
|
+
puts "#{Rainbow('•').yellow} #{Rainbow('Database issues:').white.bold} " \
|
355
|
+
"Run #{Rainbow('bin/rails db:create db:migrate').cyan}"
|
356
|
+
end
|
357
|
+
|
358
|
+
if error_content.include?("gem") || error_content.include?("bundle") || error_content.include?("load error")
|
359
|
+
puts "#{Rainbow('•').yellow} #{Rainbow('Missing dependencies:').white.bold} " \
|
360
|
+
"Run #{Rainbow('bundle install && npm install').cyan}"
|
361
|
+
end
|
362
|
+
|
363
|
+
if error_content.include?("webpack") || error_content.include?("module") ||
|
364
|
+
error_content.include?("compilation")
|
365
|
+
puts "#{Rainbow('•').yellow} #{Rainbow('Webpack compilation:').white.bold} " \
|
366
|
+
"Check JavaScript/webpack errors above"
|
367
|
+
end
|
368
|
+
|
369
|
+
# Always show these general options
|
370
|
+
puts "#{Rainbow('•').yellow} #{Rainbow('Environment config:').white} " \
|
371
|
+
"Check #{Rainbow('config/environments/production.rb').cyan}"
|
372
|
+
|
373
|
+
puts ""
|
374
|
+
puts Rainbow("ℹ️ Alternative for development:").blue
|
375
|
+
puts " #{Rainbow('bin/dev static').green} # Static assets without production optimizations"
|
376
|
+
puts ""
|
377
|
+
exit 1
|
378
|
+
end
|
379
|
+
end
|
380
|
+
# rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
|
381
|
+
|
382
|
+
def run_static_development(procfile, verbose: false, route: nil)
|
383
|
+
print_procfile_info(procfile, route: route)
|
384
|
+
print_server_info(
|
385
|
+
"⚡ Starting development server with static assets...",
|
386
|
+
[
|
387
|
+
"Generating React on Rails packs",
|
388
|
+
"Using shakapacker --watch (no HMR)",
|
389
|
+
"CSS extracted to separate files (no FOUC)",
|
390
|
+
"Development environment (source maps, faster builds)",
|
391
|
+
"Auto-recompiles on file changes"
|
392
|
+
],
|
393
|
+
route: route
|
394
|
+
)
|
395
|
+
|
396
|
+
PackGenerator.generate(verbose: verbose)
|
397
|
+
ProcessManager.ensure_procfile(procfile)
|
398
|
+
ProcessManager.run_with_process_manager(procfile)
|
399
|
+
end
|
400
|
+
|
401
|
+
def run_development(procfile, verbose: false, route: nil)
|
402
|
+
print_procfile_info(procfile, route: route)
|
403
|
+
PackGenerator.generate(verbose: verbose)
|
404
|
+
ProcessManager.ensure_procfile(procfile)
|
405
|
+
ProcessManager.run_with_process_manager(procfile)
|
406
|
+
end
|
407
|
+
|
408
|
+
def print_server_info(title, features, port = 3000, route: nil)
|
409
|
+
puts title
|
410
|
+
features.each { |feature| puts " - #{feature}" }
|
411
|
+
puts ""
|
412
|
+
puts ""
|
413
|
+
url = route ? "http://localhost:#{port}/#{route}" : "http://localhost:#{port}"
|
414
|
+
puts "💡 Access at: #{Rainbow(url).cyan.underline}"
|
415
|
+
puts ""
|
416
|
+
end
|
417
|
+
|
418
|
+
def print_procfile_info(procfile, route: nil)
|
419
|
+
port = procfile_port(procfile)
|
420
|
+
box_width = 60
|
421
|
+
url = route ? "http://localhost:#{port}/#{route}" : "http://localhost:#{port}"
|
422
|
+
|
423
|
+
puts ""
|
424
|
+
puts box_border(box_width)
|
425
|
+
puts box_empty_line(box_width)
|
426
|
+
puts format_box_line("📋 Using Procfile: #{procfile}", box_width)
|
427
|
+
puts format_box_line("🔧 Customize this file for your app's needs", box_width)
|
428
|
+
puts box_empty_line(box_width)
|
429
|
+
puts format_box_line("💡 Access at: #{Rainbow(url).cyan.underline}",
|
430
|
+
box_width)
|
431
|
+
puts box_empty_line(box_width)
|
432
|
+
puts box_bottom(box_width)
|
433
|
+
puts ""
|
434
|
+
end
|
435
|
+
|
436
|
+
def procfile_port(procfile)
|
437
|
+
procfile == "Procfile.dev-prod-assets" ? 3001 : 3000
|
438
|
+
end
|
439
|
+
|
440
|
+
def box_border(width)
|
441
|
+
"┌#{'─' * (width - 2)}┐"
|
442
|
+
end
|
443
|
+
|
444
|
+
def box_bottom(width)
|
445
|
+
"└#{'─' * (width - 2)}┘"
|
446
|
+
end
|
447
|
+
|
448
|
+
def box_empty_line(width)
|
449
|
+
"│#{' ' * (width - 2)}│"
|
450
|
+
end
|
451
|
+
|
452
|
+
def format_box_line(content, box_width)
|
453
|
+
line = "│ #{content}"
|
454
|
+
# Use visual length for colored text
|
455
|
+
visual_length = Rainbow.uncolor(line).length
|
456
|
+
padding = box_width - visual_length - 2
|
457
|
+
line + "#{' ' * padding}│"
|
458
|
+
end
|
459
|
+
|
460
|
+
# rubocop:disable Metrics/AbcSize
|
461
|
+
def help_troubleshooting
|
462
|
+
<<~TROUBLESHOOTING
|
463
|
+
#{Rainbow('🔧 TROUBLESHOOTING:').cyan.bold}
|
464
|
+
|
465
|
+
#{Rainbow('⚛️ React Refresh Issues:').yellow.bold}
|
466
|
+
#{Rainbow('If you see "$RefreshSig$ is not defined" errors:').white}
|
467
|
+
#{Rainbow('1.').green} #{Rainbow('Check that both babel plugin and webpack plugin are configured:').white}
|
468
|
+
#{Rainbow('•').yellow} #{Rainbow('babel.config.js: \'react-refresh/babel\' plugin (enabled when WEBPACK_SERVE=true)').white}
|
469
|
+
#{Rainbow('•').yellow} #{Rainbow('config/webpack/development.js: ReactRefreshWebpackPlugin (enabled when WEBPACK_SERVE=true)').white}
|
470
|
+
#{Rainbow('2.').green} #{Rainbow('Ensure you\'re running HMR mode:').white} #{Rainbow('bin/dev').green.bold} #{Rainbow('(not').white} #{Rainbow('bin/dev static').red}#{Rainbow(')').white}
|
471
|
+
#{Rainbow('3.').green} #{Rainbow('Try restarting the development server:').white} #{Rainbow('bin/dev kill && bin/dev').green.bold}
|
472
|
+
#{Rainbow('4.').green} #{Rainbow('Note: React Refresh only works in HMR mode, not static mode').white}
|
473
|
+
|
474
|
+
#{Rainbow('🚨 General Issues:').yellow.bold}
|
475
|
+
#{Rainbow('•').red} #{Rainbow('"Port already in use"').white} #{Rainbow('→ Run:').yellow} #{Rainbow('bin/dev kill').green.bold}
|
476
|
+
#{Rainbow('•').red} #{Rainbow('"Webpack compilation failed"').white} #{Rainbow('→ Check console for specific errors').white}
|
477
|
+
#{Rainbow('•').red} #{Rainbow('"Process manager not found"').white} #{Rainbow('→ Install:').yellow} #{Rainbow('brew install overmind').green.bold} #{Rainbow('(or').white} #{Rainbow('gem install foreman').green.bold}#{Rainbow(')').white}
|
478
|
+
#{Rainbow('•').red} #{Rainbow('"Assets not loading"').white} #{Rainbow('→ Verify Procfile.dev is present and check server logs').white}
|
479
|
+
|
480
|
+
#{Rainbow('📚 Need help? Visit:').blue.bold} #{Rainbow('https://www.shakacode.com/react-on-rails/docs/').cyan.underline}
|
481
|
+
TROUBLESHOOTING
|
482
|
+
end
|
483
|
+
# rubocop:enable Metrics/AbcSize
|
484
|
+
end
|
485
|
+
end
|
486
|
+
end
|
487
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "dev/server_manager"
|
4
|
+
require_relative "dev/process_manager"
|
5
|
+
require_relative "dev/pack_generator"
|
6
|
+
require_relative "dev/file_manager"
|
7
|
+
|
8
|
+
module ReactOnRails
|
9
|
+
module Dev
|
10
|
+
# Development server management for React on Rails
|
11
|
+
#
|
12
|
+
# This module provides classes to manage development servers,
|
13
|
+
# process managers, pack generation, and file cleanup.
|
14
|
+
#
|
15
|
+
# Usage:
|
16
|
+
# ReactOnRails::Dev::ServerManager.start(:development)
|
17
|
+
# ReactOnRails::Dev::ServerManager.kill_processes
|
18
|
+
# ReactOnRails::Dev::ServerManager.show_help
|
19
|
+
end
|
20
|
+
end
|