Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@ Changes since the last non-beta release.

- **CSP Nonce Support for Console Replay**: Added Content Security Policy (CSP) nonce support for the `consoleReplay` script generated during server-side rendering. When Rails CSP is configured, the console replay script will automatically include the nonce attribute, allowing it to execute under restrictive CSP policies like `script-src: 'self'`. The implementation includes cross-version Rails compatibility (5.2-7.x) and defense-in-depth nonce sanitization to prevent attribute injection attacks. [PR 2059](https://github.com/shakacode/react_on_rails/pull/2059) by [justin808](https://github.com/justin808).

#### Bug Fixes

- [PR 2085](https://github.com/shakacode/react_on_rails/pull/2085) by [justin808](https://github.com/justin808): Fix pack generation in bin/dev when running from Bundler context. Pack generation was failing with "Could not find command 'react_on_rails:generate_packs'" because Bundler was intercepting the subprocess call. The fix wraps the bundle exec call with `Bundler.with_unbundled_env` to prevent interception.

### [v16.2.0.beta.11] - 2025-11-19

#### Added
Expand Down
33 changes: 27 additions & 6 deletions lib/react_on_rails/dev/pack_generator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -141,13 +141,34 @@ def handle_rake_error(error, _silent)
end

def run_via_bundle_exec(silent: false)
if silent
system(
"bundle", "exec", "rake", "react_on_rails:generate_packs",
out: File::NULL, err: File::NULL
)
# Need to unbundle to prevent Bundler from intercepting our bundle exec call
# when already running inside a Bundler context (e.g., from bin/dev)
with_unbundled_context do
if silent
system(
"bundle", "exec", "rake", "react_on_rails:generate_packs",
out: File::NULL, err: File::NULL
)
else
system("bundle", "exec", "rake", "react_on_rails:generate_packs")
end
end
end

# DRY helper method for Bundler context switching with API compatibility
# Supports both new (with_unbundled_env) and legacy (with_clean_env) Bundler APIs
def with_unbundled_context(&block)
if defined?(Bundler)
if Bundler.respond_to?(:with_unbundled_env)
Bundler.with_unbundled_env(&block)
elsif Bundler.respond_to?(:with_clean_env)
Bundler.with_clean_env(&block)
else
# Fallback if neither method is available (very old Bundler versions)
yield
end
else
system("bundle", "exec", "rake", "react_on_rails:generate_packs")
yield
end
end
end
Expand Down
85 changes: 85 additions & 0 deletions spec/react_on_rails/dev/pack_generator_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -206,5 +206,90 @@
.with("bundle", "exec", "rake", "react_on_rails:generate_packs")
end
end

context "when calling bundle exec from within Bundler context" do
before do
# Ensure we're not in Rails context to trigger bundle exec path
hide_const("Rails") if defined?(Rails)
allow(ReactOnRails::PackerUtils).to receive(:shakapacker_precompile_hook_configured?).and_return(false)
end

it "unwraps the Bundler context before executing with with_unbundled_env" do
bundler_module = Module.new do
def self.respond_to?(method, *)
method == :with_unbundled_env
end

def self.with_unbundled_env
yield
end
end
stub_const("Bundler", bundler_module)

allow(bundler_module).to receive(:with_unbundled_env).and_yield
allow(described_class).to receive(:system)
.with("bundle", "exec", "rake", "react_on_rails:generate_packs")
.and_return(true)

described_class.generate(verbose: true)

expect(bundler_module).to have_received(:with_unbundled_env)
end

it "falls back to with_clean_env when with_unbundled_env is not available" do
bundler_module = Module.new do
def self.respond_to?(method, *)
method == :with_clean_env
end

def self.with_clean_env
yield
end
end
stub_const("Bundler", bundler_module)

allow(bundler_module).to receive(:with_clean_env).and_yield
allow(described_class).to receive(:system)
.with("bundle", "exec", "rake", "react_on_rails:generate_packs")
.and_return(true)

described_class.generate(verbose: true)

expect(bundler_module).to have_received(:with_clean_env)
end

it "executes directly when neither with_unbundled_env nor with_clean_env are available" do
bundler_module = Module.new do
def self.respond_to?(_method, *)
false
end
end
stub_const("Bundler", bundler_module)

allow(described_class).to receive(:system)
.with("bundle", "exec", "rake", "react_on_rails:generate_packs")
.and_return(true)

expect { described_class.generate(verbose: true) }
.to output(/πŸ“¦ Generating React on Rails packs.../).to_stdout_from_any_process

expect(described_class).to have_received(:system)
.with("bundle", "exec", "rake", "react_on_rails:generate_packs")
end

it "executes directly when Bundler is not defined" do
hide_const("Bundler") if defined?(Bundler)

allow(described_class).to receive(:system)
.with("bundle", "exec", "rake", "react_on_rails:generate_packs")
.and_return(true)

expect { described_class.generate(verbose: true) }
.to output(/πŸ“¦ Generating React on Rails packs.../).to_stdout_from_any_process

expect(described_class).to have_received(:system)
.with("bundle", "exec", "rake", "react_on_rails:generate_packs")
end
end
end
end
Loading