react_on_rails 16.3.0 → 16.4.0.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 0ace0aef17c3665d3f573612c0fb1298834237fab6c25c67dc609fcd844efacf
4
- data.tar.gz: 9768eec151936531996266b8635cfa9431014a98219b76c6a501ccba63a42ab2
3
+ metadata.gz: e73e8a4df085484ab44226919e7e642d67b609b03375b34845ea43f896e2ca43
4
+ data.tar.gz: c94d4c27cd4ae57c9b382b80327686d918ad2906f561879c04cc90817f89d195
5
5
  SHA512:
6
- metadata.gz: c1f027af8612019f38ab829a9b99ca50f869112f9b6f0f47fd0af27aa4bf6cabfb1ab45121ce7c4607d12f364292cecc21e8aba863b791f52d6fa557231e9fa2
7
- data.tar.gz: 4d6549c75f9615433be29f67d7ea68963e2f7480ad4468f86a9da2f50d1d490f5311ce188ddac3fb422e52cf20aaffa1d24b175214cd4321574fe616b21f42bc
6
+ metadata.gz: 5eac27c292bd94ffbdb2c61a3d64d0021b39320c7b82778df631c4e231cfafa190a943101a41d57cf053a0b52f948b3dd0f8c0db5d48cc4a7f9e12177043c770
7
+ data.tar.gz: ab3b3beeda1c9ae9d5bf21b2e6ba946d1ead835ed9e0b6fc7b3017e3cdfdb8c101aab5f25a3fd35f6486c0f2dfdd4e68927f9b0a9d0a9020e709f9e33d0a29bb
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- react_on_rails (16.3.0)
4
+ react_on_rails (16.4.0.rc.0)
5
5
  addressable
6
6
  connection_pool
7
7
  execjs (~> 2.5)
data/Steepfile CHANGED
@@ -28,6 +28,7 @@ target :lib do
28
28
  check "lib/react_on_rails.rb"
29
29
  check "lib/react_on_rails/configuration.rb"
30
30
  check "lib/react_on_rails/controller.rb"
31
+ check "lib/react_on_rails/dev/database_checker.rb"
31
32
  check "lib/react_on_rails/dev/file_manager.rb"
32
33
  check "lib/react_on_rails/dev/pack_generator.rb"
33
34
  check "lib/react_on_rails/dev/process_manager.rb"
@@ -47,11 +47,18 @@ module ReactOnRails
47
47
  File.open(hello_world_index, "w+") { |f| f.puts new_hello_world_contents }
48
48
  end
49
49
 
50
- def add_yarn_relative_install_script_in_package_json
50
+ def add_react_on_rails_as_file_dependency
51
+ # Add react-on-rails as a file dependency pointing to the local package
52
+ # This allows testing with the local npm package without needing yalc
51
53
  package_json = File.join(destination_root, "package.json")
52
54
  contents = JSON.parse(File.read(package_json))
53
- contents["scripts"] ||= {}
54
- contents["scripts"]["postinstall"] = "yalc link react-on-rails"
55
+ contents["dependencies"] ||= {}
56
+
57
+ # Calculate relative path from the generated example to the npm package
58
+ # Generated examples are in gen-examples/examples/<name>/
59
+ # The npm package is in packages/react-on-rails/
60
+ contents["dependencies"]["react-on-rails"] = "file:../../../packages/react-on-rails"
61
+
55
62
  File.open(package_json, "w+") { |f| f.puts JSON.pretty_generate(contents) }
56
63
  end
57
64
  end
@@ -2,6 +2,7 @@
2
2
 
3
3
  require "rails/generators"
4
4
  require "json"
5
+ require "bundler"
5
6
  require_relative "generator_helper"
6
7
  require_relative "generator_messages"
7
8
  require_relative "js_dependency_manager"
@@ -218,10 +219,10 @@ module ReactOnRails
218
219
  end
219
220
 
220
221
  def shakapacker_in_lockfile?(gem_name)
221
- gemfile = ENV["BUNDLE_GEMFILE"] || "Gemfile"
222
- lockfile = File.join(File.dirname(gemfile), "Gemfile.lock")
223
-
224
- File.file?(lockfile) && File.foreach(lockfile).any? { |l| l.match?(/^\s{4}#{Regexp.escape(gem_name)}\s\(/) }
222
+ # Always check the target app's Gemfile.lock, not inherited BUNDLE_GEMFILE
223
+ # See: https://github.com/shakacode/react_on_rails/issues/2287
224
+ File.file?("Gemfile.lock") &&
225
+ File.foreach("Gemfile.lock").any? { |l| l.match?(/^\s{4}#{Regexp.escape(gem_name)}\s\(/) }
225
226
  end
226
227
 
227
228
  def shakapacker_in_bundler_specs?(gem_name)
@@ -232,10 +233,10 @@ module ReactOnRails
232
233
  end
233
234
 
234
235
  def shakapacker_in_gemfile_text?(gem_name)
235
- gemfile = ENV["BUNDLE_GEMFILE"] || "Gemfile"
236
-
237
- File.file?(gemfile) &&
238
- File.foreach(gemfile).any? { |l| l.match?(/^\s*gem\s+['"]#{Regexp.escape(gem_name)}['"]/) }
236
+ # Always check the target app's Gemfile, not inherited BUNDLE_GEMFILE
237
+ # See: https://github.com/shakacode/react_on_rails/issues/2287
238
+ File.file?("Gemfile") &&
239
+ File.foreach("Gemfile").any? { |l| l.match?(/^\s*gem\s+['"]#{Regexp.escape(gem_name)}['"]/) }
239
240
  end
240
241
 
241
242
  def cli_exists?(command)
@@ -263,7 +264,9 @@ module ReactOnRails
263
264
  return if shakapacker_in_gemfile?
264
265
 
265
266
  puts Rainbow("📝 Adding Shakapacker to Gemfile...").yellow
266
- success = system("bundle add shakapacker --strict")
267
+ # Use with_unbundled_env to prevent inheriting BUNDLE_GEMFILE from parent process
268
+ # See: https://github.com/shakacode/react_on_rails/issues/2287
269
+ success = Bundler.with_unbundled_env { system("bundle add shakapacker --strict") }
267
270
  return if success
268
271
 
269
272
  handle_shakapacker_gemfile_error
@@ -273,15 +276,16 @@ module ReactOnRails
273
276
  puts Rainbow("⚙️ Installing Shakapacker (required for webpack integration)...").yellow
274
277
 
275
278
  # First run bundle install to make shakapacker available
279
+ # Use with_unbundled_env to prevent inheriting BUNDLE_GEMFILE from parent process
276
280
  puts Rainbow("📦 Running bundle install...").yellow
277
- bundle_success = system("bundle install")
281
+ bundle_success = Bundler.with_unbundled_env { system("bundle install") }
278
282
  unless bundle_success
279
283
  handle_shakapacker_install_error
280
284
  return
281
285
  end
282
286
 
283
287
  # Then run the shakapacker installer
284
- success = system("bundle exec rails shakapacker:install")
288
+ success = Bundler.with_unbundled_env { system("bundle exec rails shakapacker:install") }
285
289
  return if success
286
290
 
287
291
  handle_shakapacker_install_error
@@ -17,6 +17,49 @@
17
17
  # 2. Modify this script for project-specific command-line behavior
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
+ #
21
+ # EXTENSIBLE PRECOMPILE PATTERN (Alternative to precompile_hook)
22
+ # ===============================================================
23
+ # If your project has custom build requirements (e.g., ReScript, TypeScript
24
+ # compilation, custom locale generation), you can handle precompile tasks
25
+ # directly in this script instead of using the precompile_hook mechanism.
26
+ #
27
+ # Benefits of this approach:
28
+ # - Single place to manage all precompile tasks
29
+ # - Direct Ruby API calls (faster, no shell spawning overhead)
30
+ # - Better version manager compatibility (mise, asdf, rbenv)
31
+ # - Clean Procfiles without embedded precompile logic
32
+ #
33
+ # To use this pattern:
34
+ # 1. Uncomment and customize the run_precompile_tasks method below
35
+ # 2. Remove precompile_hook from config/shakapacker.yml if present
36
+ # 3. Uncomment the run_precompile_tasks call near the bottom of this file
37
+ #
38
+ # See documentation: https://www.shakacode.com/react-on-rails/docs/building-features/extensible-precompile-pattern
39
+ #
40
+ # def run_precompile_tasks
41
+ # require_relative "../config/environment"
42
+ #
43
+ # puts "📦 Running precompile tasks..."
44
+ #
45
+ # # Example: Build ReScript files
46
+ # # print " ReScript build... "
47
+ # # unless system("yarn res:build")
48
+ # # puts "❌"
49
+ # # exit(1)
50
+ # # end
51
+ # # puts "✅"
52
+ #
53
+ # # Example: Generate locales using direct Ruby API (faster than rake task)
54
+ # # This avoids shell spawning and version manager PATH issues.
55
+ # # Exceptions (e.g., missing directories) will bubble up and stop the server,
56
+ # # which is the desired behavior for catching configuration issues early.
57
+ # print " Locale generation... "
58
+ # ReactOnRails::Locales.compile if ReactOnRails.configuration.i18n_dir.present?
59
+ # puts "✅"
60
+ #
61
+ # puts ""
62
+ # end
20
63
 
21
64
  require "bundler/setup"
22
65
  require "react_on_rails/dev"
@@ -31,4 +74,10 @@ DEFAULT_ROUTE = "hello_world"
31
74
  argv_with_defaults = ARGV.dup
32
75
  argv_with_defaults.push("--route=#{DEFAULT_ROUTE}") unless argv_with_defaults.any? { |arg| arg.start_with?("--route") }
33
76
 
77
+ # Uncomment to run custom precompile tasks before starting the server
78
+ # Only run for server start commands (not kill/help)
79
+ # unless ARGV.include?("kill") || ARGV.include?("-h") || ARGV.include?("--help") || ARGV.include?("help")
80
+ # run_precompile_tasks
81
+ # end
82
+
34
83
  ReactOnRails::Dev::ServerManager.run_from_command_line(argv_with_defaults)
@@ -1,6 +1,11 @@
1
1
  #!/usr/bin/env ruby
2
2
  # frozen_string_literal: true
3
3
 
4
+ # Self-guard: skip if bin/dev already ran precompile tasks.
5
+ # This prevents duplicate execution in HMR mode (two webpack processes)
6
+ # regardless of Shakapacker version.
7
+ exit 0 if ENV["SHAKAPACKER_SKIP_PRECOMPILE_HOOK"] == "true"
8
+
4
9
  # Shakapacker precompile hook for React on Rails
5
10
  #
6
11
  # This script runs before webpack compilation to generate pack files
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # React on Rails configuration
4
- # See https://github.com/shakacode/react_on_rails/blob/master/docs/configuration/configuration.md
4
+ # See https://github.com/shakacode/react_on_rails/blob/master/docs/configuration/README.md
5
5
  # for complete documentation of all configuration options.
6
6
 
7
7
  ReactOnRails.configure do |config|
@@ -43,7 +43,7 @@ ReactOnRails.configure do |config|
43
43
  # ALTERNATIVE APPROACH: Uncomment below AND configure ReactOnRails::TestHelper
44
44
  # - Provides explicit control over test asset compilation timing
45
45
  # - Requires adding ReactOnRails::TestHelper to spec/rails_helper.rb
46
- # - See: https://github.com/shakacode/react_on_rails/blob/master/docs/guides/testing-configuration.md
46
+ # - See: https://github.com/shakacode/react_on_rails/blob/master/docs/building-features/testing-configuration.md
47
47
  #
48
48
  config.build_test_command = "RAILS_ENV=test bin/shakapacker"
49
49
 
@@ -62,5 +62,5 @@ ReactOnRails.configure do |config|
62
62
  # - Custom rendering extensions
63
63
  # - And more...
64
64
  #
65
- # See: https://github.com/shakacode/react_on_rails/blob/master/docs/configuration/configuration.md
65
+ # See: https://github.com/shakacode/react_on_rails/blob/master/docs/configuration/README.md
66
66
  end
@@ -58,6 +58,7 @@ module ReactOnRails
58
58
  same_bundle_for_client_and_server: false,
59
59
  i18n_output_format: nil,
60
60
  components_subdirectory: nil,
61
+ stores_subdirectory: nil,
61
62
  make_generated_server_bundle_the_entrypoint: false,
62
63
  defer_generated_component_packs: false,
63
64
  # Maximum time in milliseconds to wait for client-side component registration after page load.
@@ -66,7 +67,11 @@ module ReactOnRails
66
67
  component_registry_timeout: DEFAULT_COMPONENT_REGISTRY_TIMEOUT,
67
68
  generated_component_packs_loading_strategy: nil,
68
69
  server_bundle_output_path: DEFAULT_SERVER_BUNDLE_OUTPUT_PATH,
69
- enforce_private_server_bundles: false
70
+ enforce_private_server_bundles: false,
71
+ # Whether to check database connectivity before starting bin/dev.
72
+ # Set to false to disable (saves ~1-2 seconds startup time).
73
+ # Can also be disabled via SKIP_DATABASE_CHECK=true or bin/dev --skip-database-check
74
+ check_database_on_dev_start: true
70
75
  )
71
76
  end
72
77
 
@@ -75,6 +80,7 @@ module ReactOnRails
75
80
  :trace, :development_mode, :logging_on_server, :server_renderer_pool_size,
76
81
  :server_renderer_timeout, :skip_display_none, :raise_on_prerender_error,
77
82
  :generated_assets_dirs, :generated_assets_dir, :components_subdirectory,
83
+ :stores_subdirectory,
78
84
  :webpack_generated_files, :rendering_extension, :build_test_command,
79
85
  :build_production_command, :i18n_dir, :i18n_yml_dir, :i18n_output_format,
80
86
  :i18n_yml_safe_load_options, :defer_generated_component_packs,
@@ -83,7 +89,8 @@ module ReactOnRails
83
89
  :make_generated_server_bundle_the_entrypoint,
84
90
  :generated_component_packs_loading_strategy,
85
91
  :component_registry_timeout,
86
- :server_bundle_output_path, :enforce_private_server_bundles
92
+ :server_bundle_output_path, :enforce_private_server_bundles,
93
+ :check_database_on_dev_start
87
94
 
88
95
  # Class instance variable and mutex to track if deprecation warning has been shown
89
96
  # Using mutex to ensure thread-safety in multi-threaded environments
@@ -143,8 +150,9 @@ module ReactOnRails
143
150
  same_bundle_for_client_and_server: nil,
144
151
  i18n_dir: nil, i18n_yml_dir: nil, i18n_output_format: nil, i18n_yml_safe_load_options: nil,
145
152
  random_dom_id: nil, server_render_method: nil, rendering_props_extension: nil,
146
- components_subdirectory: nil, auto_load_bundle: nil,
147
- component_registry_timeout: nil, server_bundle_output_path: nil, enforce_private_server_bundles: nil)
153
+ components_subdirectory: nil, stores_subdirectory: nil, auto_load_bundle: nil,
154
+ component_registry_timeout: nil, server_bundle_output_path: nil, enforce_private_server_bundles: nil,
155
+ check_database_on_dev_start: nil)
148
156
  self.node_modules_location = node_modules_location.present? ? node_modules_location : Rails.root
149
157
  self.generated_assets_dirs = generated_assets_dirs
150
158
  self.generated_assets_dir = generated_assets_dir
@@ -181,12 +189,14 @@ module ReactOnRails
181
189
 
182
190
  self.server_render_method = server_render_method
183
191
  self.components_subdirectory = components_subdirectory
192
+ self.stores_subdirectory = stores_subdirectory
184
193
  self.auto_load_bundle = auto_load_bundle
185
194
  self.make_generated_server_bundle_the_entrypoint = make_generated_server_bundle_the_entrypoint
186
195
  self.defer_generated_component_packs = defer_generated_component_packs
187
196
  self.generated_component_packs_loading_strategy = generated_component_packs_loading_strategy
188
197
  self.server_bundle_output_path = server_bundle_output_path
189
198
  self.enforce_private_server_bundles = enforce_private_server_bundles
199
+ self.check_database_on_dev_start = check_database_on_dev_start.nil? ? true : check_database_on_dev_start
190
200
  end
191
201
  # rubocop:enable Metrics/AbcSize
192
202
 
@@ -0,0 +1,168 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "open3"
4
+ require "rainbow"
5
+
6
+ module ReactOnRails
7
+ module Dev
8
+ # DatabaseChecker validates that the Rails database is properly set up
9
+ # before starting the development server.
10
+ #
11
+ # This prevents confusing errors when running bin/dev on a fresh checkout
12
+ # or after database cleanup.
13
+ #
14
+ # Checks performed:
15
+ # 1. Database exists and is accessible
16
+ # 2. Migrations are up to date (optional warning)
17
+ #
18
+ # Can be disabled via:
19
+ # - Environment variable: SKIP_DATABASE_CHECK=true
20
+ # - CLI flag: bin/dev --skip-database-check
21
+ # - Configuration: ReactOnRails.configure { |c| c.check_database_on_dev_start = false }
22
+ #
23
+ # Note: This check spawns a Rails runner process which adds ~1-2 seconds to startup.
24
+ # Disable it if this overhead is unacceptable for your workflow.
25
+ #
26
+ class DatabaseChecker
27
+ class << self
28
+ # Check if the database is set up and accessible
29
+ #
30
+ # @param skip [Boolean] if true, skip the check entirely
31
+ # @return [Boolean] true if database is ready (or check was skipped), false otherwise
32
+ def check_database(skip: false)
33
+ return true if should_skip_check?(skip)
34
+
35
+ print_checking_message
36
+ result = run_database_check
37
+
38
+ case result[:status]
39
+ when :ok
40
+ print_database_ok
41
+ check_pending_migrations
42
+ true
43
+ when :error
44
+ print_database_error(result[:error])
45
+ false
46
+ when :not_rails_app
47
+ print_skipped_message("bin/rails not found — skipping database check")
48
+ true
49
+ else
50
+ print_skipped_message(result[:error] || "unexpected status: #{result[:status]}")
51
+ true
52
+ end
53
+ end
54
+
55
+ private
56
+
57
+ def should_skip_check?(skip_flag)
58
+ # 1. CLI flag takes highest priority
59
+ return true if skip_flag
60
+
61
+ # 2. Environment variable
62
+ return true if ENV["SKIP_DATABASE_CHECK"] == "true"
63
+
64
+ # 3. ReactOnRails configuration (if available)
65
+ return true if defined?(ReactOnRails) &&
66
+ ReactOnRails.respond_to?(:configuration) &&
67
+ ReactOnRails.configuration.check_database_on_dev_start == false
68
+
69
+ false
70
+ end
71
+
72
+ def run_database_check
73
+ check_script = <<~RUBY
74
+ unless defined?(ActiveRecord)
75
+ puts 'DATABASE_OK' # No ActiveRecord = no database to check
76
+ exit
77
+ end
78
+ begin
79
+ ActiveRecord::Base.connection.execute('SELECT 1')
80
+ puts 'DATABASE_OK'
81
+ rescue StandardError => e
82
+ puts 'DATABASE_ERROR'
83
+ puts e.message
84
+ end
85
+ RUBY
86
+
87
+ # SECURITY: Using array form of Open3.capture3 is safe from command injection.
88
+ # The check_script is hardcoded above and never includes user input.
89
+ stdout, stderr, status = Open3.capture3("bin/rails", "runner", check_script)
90
+ parse_check_result(stdout, stderr, status)
91
+ rescue Errno::ENOENT
92
+ { status: :not_rails_app }
93
+ rescue StandardError => e
94
+ { status: :unknown_error, error: e.message }
95
+ end
96
+
97
+ def parse_check_result(stdout, stderr, status)
98
+ return { status: :error, error: "#{stdout}\n#{stderr}".strip } unless status.success?
99
+
100
+ lines = stdout.strip.split("\n")
101
+
102
+ # Empty output with successful exit means the process completed without error.
103
+ # This can happen when ActiveRecord is not loaded or when Rails runner
104
+ # exits cleanly without printing anything.
105
+ return { status: :ok } if lines.empty?
106
+
107
+ case lines.first
108
+ when "DATABASE_OK" then { status: :ok }
109
+ when "DATABASE_ERROR" then { status: :error, error: lines[1..].join("\n") }
110
+ else
111
+ if lines.any? { |line| line.strip == "DATABASE_OK" }
112
+ { status: :ok }
113
+ else
114
+ { status: :error, error: "#{stdout}\n#{stderr}".strip }
115
+ end
116
+ end
117
+ end
118
+
119
+ def check_pending_migrations
120
+ stdout, _stderr, status = Open3.capture3("bin/rails", "db:migrate:status")
121
+ return unless status.success? && stdout.include?(" down ")
122
+
123
+ print_pending_migrations_warning
124
+ rescue StandardError => e
125
+ # Ignore errors - this is just a helpful warning.
126
+ # Common case: schema_migrations table doesn't exist yet on brand new databases.
127
+ warn "[ReactOnRails] Migration check failed: #{e.message}" if ENV["DEBUG"] == "1"
128
+ nil
129
+ end
130
+
131
+ def print_checking_message
132
+ puts "\n#{Rainbow('Checking database setup...').cyan}"
133
+ end
134
+
135
+ def print_database_ok
136
+ puts Rainbow(" Database is accessible").green
137
+ end
138
+
139
+ def print_skipped_message(reason)
140
+ puts Rainbow(" #{reason}").yellow
141
+ end
142
+
143
+ def print_pending_migrations_warning
144
+ puts Rainbow(" Pending migrations detected").yellow
145
+ puts Rainbow(" Run: bin/rails db:migrate").yellow
146
+ end
147
+
148
+ def print_database_error(error)
149
+ puts Rainbow(" Database check failed").red
150
+ puts ""
151
+ puts "#{Rainbow(" Error: #{truncate_error(error)}").red}\n" if error && !error.empty?
152
+ puts Rainbow("To fix, run:").yellow
153
+ puts ""
154
+ puts " #{Rainbow('bin/rails db:prepare').green} # Creates database if needed and runs pending migrations"
155
+ puts ""
156
+ puts Rainbow("Then run bin/dev again.").blue
157
+ puts ""
158
+ end
159
+
160
+ def truncate_error(error)
161
+ return error if error.length <= 500
162
+
163
+ "#{error[0, 500]}...\n (Set DEBUG=1 to see full error)"
164
+ end
165
+ end
166
+ end
167
+ end
168
+ end