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
@@ -18,29 +18,17 @@
|
|
18
18
|
# 3. Extend ReactOnRails::Dev classes in your Rails app for advanced customization
|
19
19
|
# 4. Use classes directly: ReactOnRails::Dev::ServerManager.start(:development, "Custom.procfile")
|
20
20
|
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
end
|
21
|
+
require "bundler/setup"
|
22
|
+
require "react_on_rails/dev"
|
23
|
+
|
24
|
+
# Default route configuration
|
25
|
+
# This is set by the ReactOnRails installer to point to your generated component.
|
26
|
+
# Change this to your preferred default route, or pass --route=<route> to override.
|
27
|
+
DEFAULT_ROUTE = "hello_world"
|
29
28
|
|
30
29
|
# Main execution
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
when "kill"
|
37
|
-
ReactOnRails::Dev::ServerManager.kill_processes
|
38
|
-
when "help", "--help", "-h"
|
39
|
-
ReactOnRails::Dev::ServerManager.show_help
|
40
|
-
when "hmr", nil
|
41
|
-
ReactOnRails::Dev::ServerManager.start(:development, "Procfile.dev")
|
42
|
-
else
|
43
|
-
puts "Unknown argument: #{ARGV[0]}"
|
44
|
-
puts "Run 'bin/dev help' for usage information"
|
45
|
-
exit 1
|
46
|
-
end
|
30
|
+
# Add the default route to ARGV if no --route option is provided
|
31
|
+
argv_with_defaults = ARGV.dup
|
32
|
+
argv_with_defaults.push("--route", DEFAULT_ROUTE) unless argv_with_defaults.any? { |arg| arg.start_with?("--route") }
|
33
|
+
|
34
|
+
ReactOnRails::Dev::ServerManager.run_from_command_line(argv_with_defaults)
|
@@ -0,0 +1,18 @@
|
|
1
|
+
/* eslint-disable import/prefer-default-export */
|
2
|
+
|
3
|
+
import { HELLO_WORLD_NAME_UPDATE } from '../constants/helloWorldConstants';
|
4
|
+
|
5
|
+
// Action interface
|
6
|
+
export interface UpdateNameAction {
|
7
|
+
type: typeof HELLO_WORLD_NAME_UPDATE;
|
8
|
+
text: string;
|
9
|
+
}
|
10
|
+
|
11
|
+
// Union type for all actions
|
12
|
+
export type HelloWorldAction = UpdateNameAction;
|
13
|
+
|
14
|
+
// Action creator with proper TypeScript typing
|
15
|
+
export const updateName = (text: string): UpdateNameAction => ({
|
16
|
+
type: HELLO_WORLD_NAME_UPDATE,
|
17
|
+
text,
|
18
|
+
});
|
@@ -0,0 +1,24 @@
|
|
1
|
+
import React from 'react';
|
2
|
+
import * as style from './HelloWorld.module.css';
|
3
|
+
import type { PropsFromRedux } from '../containers/HelloWorldContainer';
|
4
|
+
|
5
|
+
// Component props are inferred from Redux container
|
6
|
+
type HelloWorldProps = PropsFromRedux;
|
7
|
+
|
8
|
+
const HelloWorld: React.FC<HelloWorldProps> = ({ name, updateName }) => (
|
9
|
+
<div>
|
10
|
+
<h3>
|
11
|
+
Hello,
|
12
|
+
{name}!
|
13
|
+
</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) => updateName(e.target.value)} />
|
19
|
+
</label>
|
20
|
+
</form>
|
21
|
+
</div>
|
22
|
+
);
|
23
|
+
|
24
|
+
export default HelloWorld;
|
@@ -0,0 +1,20 @@
|
|
1
|
+
// Simple example of a React "smart" component
|
2
|
+
|
3
|
+
import { connect, ConnectedProps } from 'react-redux';
|
4
|
+
import HelloWorld from '../components/HelloWorld';
|
5
|
+
import * as actions from '../actions/helloWorldActionCreators';
|
6
|
+
import type { HelloWorldState } from '../reducers/helloWorldReducer';
|
7
|
+
|
8
|
+
// Which part of the Redux global state does our component want to receive as props?
|
9
|
+
const mapStateToProps = (state: HelloWorldState) => ({ name: state.name });
|
10
|
+
|
11
|
+
// Create the connector
|
12
|
+
const connector = connect(mapStateToProps, actions);
|
13
|
+
|
14
|
+
// Infer the props from Redux state and actions
|
15
|
+
export type PropsFromRedux = ConnectedProps<typeof connector>;
|
16
|
+
|
17
|
+
// Don't forget to actually use connect!
|
18
|
+
// Note that we don't export HelloWorld, but the redux "connected" version of it.
|
19
|
+
// See https://github.com/reactjs/react-redux/blob/master/docs/api.md#examples
|
20
|
+
export default connector(HelloWorld);
|
@@ -0,0 +1,22 @@
|
|
1
|
+
import { combineReducers } from 'redux';
|
2
|
+
import { HELLO_WORLD_NAME_UPDATE } from '../constants/helloWorldConstants';
|
3
|
+
import { HelloWorldAction } from '../actions/helloWorldActionCreators';
|
4
|
+
|
5
|
+
// State interface
|
6
|
+
export interface HelloWorldState {
|
7
|
+
name: string;
|
8
|
+
}
|
9
|
+
|
10
|
+
// Individual reducer with TypeScript types
|
11
|
+
const name = (state: string = '', action: HelloWorldAction): string => {
|
12
|
+
switch (action.type) {
|
13
|
+
case HELLO_WORLD_NAME_UPDATE:
|
14
|
+
return action.text;
|
15
|
+
default:
|
16
|
+
return state;
|
17
|
+
}
|
18
|
+
};
|
19
|
+
|
20
|
+
const helloWorldReducer = combineReducers<HelloWorldState>({ name });
|
21
|
+
|
22
|
+
export default helloWorldReducer;
|
@@ -0,0 +1,23 @@
|
|
1
|
+
import { useMemo, type FC } from 'react';
|
2
|
+
import { Provider } from 'react-redux';
|
3
|
+
|
4
|
+
import configureStore, { type RailsProps } from '../store/helloWorldStore';
|
5
|
+
import HelloWorldContainer from '../containers/HelloWorldContainer';
|
6
|
+
|
7
|
+
// Props interface matches what Rails will pass from the controller
|
8
|
+
interface HelloWorldAppProps extends RailsProps {}
|
9
|
+
|
10
|
+
// See documentation for https://github.com/reactjs/react-redux.
|
11
|
+
// This is how you get props from the Rails view into the redux store.
|
12
|
+
// This code here binds your smart component to the redux store.
|
13
|
+
const HelloWorldApp: FC<HelloWorldAppProps> = (props) => {
|
14
|
+
const store = useMemo(() => configureStore(props), [props]);
|
15
|
+
|
16
|
+
return (
|
17
|
+
<Provider store={store}>
|
18
|
+
<HelloWorldContainer />
|
19
|
+
</Provider>
|
20
|
+
);
|
21
|
+
};
|
22
|
+
|
23
|
+
export default HelloWorldApp;
|
@@ -0,0 +1,18 @@
|
|
1
|
+
import { createStore } from 'redux';
|
2
|
+
import type { Store, PreloadedState } from 'redux';
|
3
|
+
import helloWorldReducer from '../reducers/helloWorldReducer';
|
4
|
+
import type { HelloWorldState } from '../reducers/helloWorldReducer';
|
5
|
+
|
6
|
+
// Rails props interface - customize based on your Rails controller
|
7
|
+
export interface RailsProps {
|
8
|
+
name: string;
|
9
|
+
[key: string]: any; // Allow additional props from Rails
|
10
|
+
}
|
11
|
+
|
12
|
+
// Store type
|
13
|
+
export type HelloWorldStore = Store<HelloWorldState>;
|
14
|
+
|
15
|
+
const configureStore = (railsProps: RailsProps): HelloWorldStore =>
|
16
|
+
createStore(helloWorldReducer, railsProps as PreloadedState<HelloWorldState>);
|
17
|
+
|
18
|
+
export default configureStore;
|
@@ -175,7 +175,7 @@ module ReactOnRails
|
|
175
175
|
end
|
176
176
|
|
177
177
|
msg = <<~MSG
|
178
|
-
ReactOnRails: Your current version of
|
178
|
+
ReactOnRails: Your current version of shakapacker \
|
179
179
|
does not support async script loading, which may cause performance issues. Please either:
|
180
180
|
1. Use :sync or :defer loading strategy instead of :async
|
181
181
|
2. Upgrade to Shakapacker v8.2.0 or above to enable async script loading
|
@@ -284,8 +284,10 @@ module ReactOnRails
|
|
284
284
|
if ReactOnRails::PackerUtils.using_packer?
|
285
285
|
packer_public_output_path = ReactOnRails::PackerUtils.packer_public_output_path
|
286
286
|
# rubocop:disable Layout/LineLength
|
287
|
+
packer_name = ReactOnRails::PackerUtils.packer_type&.upcase_first
|
288
|
+
|
287
289
|
Rails.logger.warn "Error configuring config/initializers/react_on_rails. Define neither the generated_assets_dirs nor " \
|
288
|
-
"the generated_assets_dir when using #{
|
290
|
+
"the generated_assets_dir when using #{packer_name}. This is defined by " \
|
289
291
|
"public_output_path specified in #{ReactOnRails::PackerUtils.packer_type}.yml = #{packer_public_output_path}."
|
290
292
|
# rubocop:enable Layout/LineLength
|
291
293
|
return
|
@@ -331,15 +333,17 @@ module ReactOnRails
|
|
331
333
|
end
|
332
334
|
|
333
335
|
def compile_command_conflict_message
|
336
|
+
packer_name = ReactOnRails::PackerUtils.packer_type.upcase_first
|
337
|
+
packer_type = ReactOnRails::PackerUtils.packer_type
|
334
338
|
<<~MSG
|
335
339
|
|
336
|
-
React on Rails and #{
|
340
|
+
React on Rails and #{packer_name} error in configuration!
|
337
341
|
In order to use config/react_on_rails.rb config.build_production_command,
|
338
|
-
you must edit config/#{
|
339
|
-
'#{
|
342
|
+
you must edit config/#{packer_type}.yml to include this value in the default configuration:
|
343
|
+
'#{packer_type}_precompile: false'
|
340
344
|
|
341
345
|
Alternatively, remove the config/react_on_rails.rb config.build_production_command and the
|
342
|
-
default bin/#{
|
346
|
+
default bin/#{packer_type} script will be used for assets:precompile.
|
343
347
|
|
344
348
|
MSG
|
345
349
|
end
|
@@ -8,16 +8,16 @@ module ReactOnRails
|
|
8
8
|
module Dev
|
9
9
|
class ServerManager
|
10
10
|
class << self
|
11
|
-
def start(mode = :development, procfile = nil, verbose: false)
|
11
|
+
def start(mode = :development, procfile = nil, verbose: false, route: nil, rails_env: nil)
|
12
12
|
case mode
|
13
13
|
when :production_like
|
14
|
-
run_production_like(_verbose: verbose)
|
14
|
+
run_production_like(_verbose: verbose, route: route, rails_env: rails_env)
|
15
15
|
when :static
|
16
16
|
procfile ||= "Procfile.dev-static-assets"
|
17
|
-
run_static_development(procfile, verbose: verbose)
|
17
|
+
run_static_development(procfile, verbose: verbose, route: route)
|
18
18
|
when :development, :hmr
|
19
19
|
procfile ||= "Procfile.dev"
|
20
|
-
run_development(procfile, verbose: verbose)
|
20
|
+
run_development(procfile, verbose: verbose, route: route)
|
21
21
|
else
|
22
22
|
raise ArgumentError, "Unknown mode: #{mode}"
|
23
23
|
end
|
@@ -116,6 +116,50 @@ module ReactOnRails
|
|
116
116
|
puts help_troubleshooting
|
117
117
|
end
|
118
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
|
+
|
119
163
|
private
|
120
164
|
|
121
165
|
def help_usage
|
@@ -142,12 +186,21 @@ module ReactOnRails
|
|
142
186
|
end
|
143
187
|
# rubocop:enable Metrics/AbcSize
|
144
188
|
|
189
|
+
# rubocop:disable Metrics/AbcSize
|
145
190
|
def help_options
|
146
191
|
<<~OPTIONS
|
147
192
|
#{Rainbow('⚙️ OPTIONS:').cyan.bold}
|
148
|
-
#{Rainbow('--
|
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}
|
149
201
|
OPTIONS
|
150
202
|
end
|
203
|
+
# rubocop:enable Metrics/AbcSize
|
151
204
|
|
152
205
|
def help_customization
|
153
206
|
<<~CUSTOMIZATION
|
@@ -172,7 +225,7 @@ module ReactOnRails
|
|
172
225
|
#{Rainbow('•').yellow} #{Rainbow('Source maps for debugging').white}
|
173
226
|
#{Rainbow('•').yellow} #{Rainbow('May have Flash of Unstyled Content (FOUC)').white}
|
174
227
|
#{Rainbow('•').yellow} #{Rainbow('Fast recompilation').white}
|
175
|
-
#{Rainbow('•').yellow} #{Rainbow('Access at:').white} #{Rainbow('http://localhost:3000
|
228
|
+
#{Rainbow('•').yellow} #{Rainbow('Access at:').white} #{Rainbow('http://localhost:3000/<route>').cyan.underline}
|
176
229
|
|
177
230
|
#{Rainbow('📦 Static development mode').cyan.bold} - #{Rainbow('Procfile.dev-static-assets').green}:
|
178
231
|
#{Rainbow('•').yellow} #{Rainbow('No HMR (static assets with auto-recompilation)').white}
|
@@ -181,24 +234,26 @@ module ReactOnRails
|
|
181
234
|
#{Rainbow('•').yellow} #{Rainbow('CSS extracted to separate files (no FOUC)').white}
|
182
235
|
#{Rainbow('•').yellow} #{Rainbow('Development environment (faster builds than production)').white}
|
183
236
|
#{Rainbow('•').yellow} #{Rainbow('Source maps for debugging').white}
|
184
|
-
#{Rainbow('•').yellow} #{Rainbow('Access at:').white} #{Rainbow('http://localhost:3000
|
237
|
+
#{Rainbow('•').yellow} #{Rainbow('Access at:').white} #{Rainbow('http://localhost:3000/<route>').cyan.underline}
|
185
238
|
|
186
239
|
#{Rainbow('🏭 Production-assets mode').cyan.bold} - #{Rainbow('Procfile.dev-prod-assets').green}:
|
187
240
|
#{Rainbow('•').yellow} #{Rainbow('React on Rails pack generation before Procfile start').white}
|
188
|
-
#{Rainbow('•').yellow} #{Rainbow('Asset precompilation with production optimizations').white}
|
189
|
-
#{Rainbow('•').yellow} #{Rainbow('
|
190
|
-
#{Rainbow('•').yellow} #{Rainbow('
|
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}
|
191
246
|
#{Rainbow('•').yellow} #{Rainbow('No HMR (static assets)').white}
|
192
|
-
#{Rainbow('•').yellow} #{Rainbow('
|
193
|
-
#{Rainbow('•').yellow} #{Rainbow('Access at:').white} #{Rainbow('http://localhost:3001/hello_world').cyan.underline}
|
247
|
+
#{Rainbow('•').yellow} #{Rainbow('Access at:').white} #{Rainbow('http://localhost:3001/<route>').cyan.underline}
|
194
248
|
MODES
|
195
249
|
end
|
196
250
|
# rubocop:enable Metrics/AbcSize
|
197
251
|
|
198
|
-
|
252
|
+
# rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
|
253
|
+
def run_production_like(_verbose: false, route: nil, rails_env: nil)
|
199
254
|
procfile = "Procfile.dev-prod-assets"
|
200
255
|
|
201
|
-
print_procfile_info(procfile)
|
256
|
+
print_procfile_info(procfile, route: route)
|
202
257
|
print_server_info(
|
203
258
|
"🏭 Starting production-like development server...",
|
204
259
|
[
|
@@ -208,25 +263,124 @@ module ReactOnRails
|
|
208
263
|
"No HMR (Hot Module Replacement)",
|
209
264
|
"CSS extracted to separate files (no FOUC)"
|
210
265
|
],
|
211
|
-
3001
|
266
|
+
3001,
|
267
|
+
route: route
|
212
268
|
)
|
213
269
|
|
214
|
-
# Precompile assets
|
215
|
-
|
216
|
-
|
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 ""
|
217
304
|
|
218
|
-
|
305
|
+
# Capture both stdout and stderr
|
306
|
+
require "open3"
|
307
|
+
stdout, stderr, status = Open3.capture3(env, *argv)
|
308
|
+
|
309
|
+
if status.success?
|
219
310
|
puts "✅ Assets precompiled successfully"
|
220
311
|
ProcessManager.ensure_procfile(procfile)
|
221
312
|
ProcessManager.run_with_process_manager(procfile)
|
222
313
|
else
|
223
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 ""
|
224
377
|
exit 1
|
225
378
|
end
|
226
379
|
end
|
380
|
+
# rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
|
227
381
|
|
228
|
-
def run_static_development(procfile, verbose: false)
|
229
|
-
print_procfile_info(procfile)
|
382
|
+
def run_static_development(procfile, verbose: false, route: nil)
|
383
|
+
print_procfile_info(procfile, route: route)
|
230
384
|
print_server_info(
|
231
385
|
"⚡ Starting development server with static assets...",
|
232
386
|
[
|
@@ -235,7 +389,8 @@ module ReactOnRails
|
|
235
389
|
"CSS extracted to separate files (no FOUC)",
|
236
390
|
"Development environment (source maps, faster builds)",
|
237
391
|
"Auto-recompiles on file changes"
|
238
|
-
]
|
392
|
+
],
|
393
|
+
route: route
|
239
394
|
)
|
240
395
|
|
241
396
|
PackGenerator.generate(verbose: verbose)
|
@@ -243,25 +398,27 @@ module ReactOnRails
|
|
243
398
|
ProcessManager.run_with_process_manager(procfile)
|
244
399
|
end
|
245
400
|
|
246
|
-
def run_development(procfile, verbose: false)
|
247
|
-
print_procfile_info(procfile)
|
401
|
+
def run_development(procfile, verbose: false, route: nil)
|
402
|
+
print_procfile_info(procfile, route: route)
|
248
403
|
PackGenerator.generate(verbose: verbose)
|
249
404
|
ProcessManager.ensure_procfile(procfile)
|
250
405
|
ProcessManager.run_with_process_manager(procfile)
|
251
406
|
end
|
252
407
|
|
253
|
-
def print_server_info(title, features, port = 3000)
|
408
|
+
def print_server_info(title, features, port = 3000, route: nil)
|
254
409
|
puts title
|
255
410
|
features.each { |feature| puts " - #{feature}" }
|
256
411
|
puts ""
|
257
412
|
puts ""
|
258
|
-
|
413
|
+
url = route ? "http://localhost:#{port}/#{route}" : "http://localhost:#{port}"
|
414
|
+
puts "💡 Access at: #{Rainbow(url).cyan.underline}"
|
259
415
|
puts ""
|
260
416
|
end
|
261
417
|
|
262
|
-
def print_procfile_info(procfile)
|
418
|
+
def print_procfile_info(procfile, route: nil)
|
263
419
|
port = procfile_port(procfile)
|
264
420
|
box_width = 60
|
421
|
+
url = route ? "http://localhost:#{port}/#{route}" : "http://localhost:#{port}"
|
265
422
|
|
266
423
|
puts ""
|
267
424
|
puts box_border(box_width)
|
@@ -269,7 +426,7 @@ module ReactOnRails
|
|
269
426
|
puts format_box_line("📋 Using Procfile: #{procfile}", box_width)
|
270
427
|
puts format_box_line("🔧 Customize this file for your app's needs", box_width)
|
271
428
|
puts box_empty_line(box_width)
|
272
|
-
puts format_box_line("💡 Access at: #{Rainbow(
|
429
|
+
puts format_box_line("💡 Access at: #{Rainbow(url).cyan.underline}",
|
273
430
|
box_width)
|
274
431
|
puts box_empty_line(box_width)
|
275
432
|
puts box_bottom(box_width)
|