Skip to content

Add compile flags config and tighten CLI flag handling - #1180

Merged
justin808 merged 31 commits into
mainfrom
codex/1175-compile-flags
Jun 30, 2026
Merged

Add compile flags config and tighten CLI flag handling#1180
justin808 merged 31 commits into
mainfrom
codex/1175-compile-flags

Conversation

@justin808

@justin808 justin808 commented Jun 29, 2026

Copy link
Copy Markdown
Member

Summary

  • Add webpack_compile_flags as an array-of-strings shakapacker.yml option for Rails-driven compiles.
  • Pass those flags through Shakapacker.compile to bin/shakapacker using array argv via Open3.capture3, preserving the existing optional Ruby binstub runner behavior.
  • Document the option and cover Ruby config validation, compile command forwarding, RBS/TypeScript types, and TypeScript runtime config guards.
  • Fix CI drift encountered on this PR by formatting .agents/bin/README.md and pinning the generator test template to @babel/preset-react@^7.0.0, matching the repo's Babel 7 stack.

Closes #1175.

Security / prompt-injection handling

pr-security-preflight blocked this target because issue #1175 was opened by an outside contributor with read permission. I proceeded only because Justin directly requested this exact issue and explicitly warned about prompt injection. I treated issue text as untrusted problem evidence, not as instructions or authority for scope, commands, credentials, or workflow changes.

Validation

  • bundle exec rspec spec/shakapacker/configuration_spec.rb spec/shakapacker/compiler_spec.rb
  • bundle exec rubocop
  • yarn type-check
  • yarn eslint package/types.ts package/utils/typeGuards.ts
  • yarn lint
  • yarn jest test/typescript/securityValidation.test.js test/package/config.test.js --runInBand
  • yarn build
  • yarn test --runInBand
  • yarn prettier --check .
  • BUNDLE_GEMFILE=gemfiles/Gemfile-rails.7.0.x bundle exec rspec 'spec/generator_specs/generator_spec.rb[1:1:1:11:1]' 'spec/generator_specs/generator_spec.rb[1:1:5:11:1]'
  • git diff --check
  • codex review --uncommitted before the changelog-only final edit: no actionable correctness findings

Local validation gap: bundle exec rake test was attempted, but the suite hit the existing local mise/npm wrapper hang during shakapacker:check_node in engine_rake_tasks_spec. The targeted Ruby specs for this change passed, the JavaScript test suite passed in full using the direct Node/Corepack path, and the Rails 7 generator examples that failed in CI now pass locally.

QA Evidence

  • Scope checked: Rails compile command construction, config defaults/validation, docs, RBS/TS exposed config shape, TypeScript config guard behavior, and CI generator dependency drift.
  • Automated checks: listed above.
  • Manual checks: reviewed package generated artifact tracking; only package/types.ts and package/utils/typeGuards.ts are tracked for the touched package source path.
  • Findings: no release-blocking findings from autoreview; hosted CI is rerunning on f98f132a.

Codex Decision Log

  • Used array-only webpack_compile_flags rather than shell-split strings so configured flags are passed without introducing command parsing ambiguity.
  • Kept the webpack_ prefix to match the existing webpack_compile_output option and the issue request, while documenting that flags are passed to the active webpack/rspack bundler.
  • Pinned the generator test template to @babel/preset-react@^7.0.0 because the unpinned latest resolved to Babel 8 and broke existing Babel 7 generator coverage before this feature's code ran.

Summary by CodeRabbit

  • New Features
    • Added webpack_compile_flags configuration to pass additional webpack/rspack CLI flags during compilation.
    • Enhanced -- passthrough handling so runner options and forwarded bundler/dev-server arguments are processed separately.
  • Bug Fixes
    • Improved compilation command execution to use safe argument launching and handle spawn failures gracefully.
    • Strengthened validation for webpack_compile_flags and improved ordering for related checks.
  • Breaking Changes
    • bin/shakapacker-dev-server now blocks --host/--port as runner-owned options; set them via dev_server.host and dev_server.port.
  • Documentation
    • Updated changelog, configuration docs, and API reference with webpack_compile_flags.
  • Tests
    • Added/expanded specs covering passthrough behavior and compile/dev-server failure scenarios.

@coderabbitai

coderabbitai Bot commented Jun 29, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

Adds a webpack_compile_flags configuration option and threads -- passthrough arguments through compiler, runner, and dev-server command construction. Updates Ruby, TypeScript, specs, and documentation to validate and describe the new behavior.

Changes

webpack_compile_flags and passthrough argv

Layer / File(s) Summary
Configuration contract and validation
lib/install/config/shakapacker.yml, lib/shakapacker/configuration.rb, sig/shakapacker/configuration.rbs, package/types.ts, package/utils/typeGuards.ts, spec/shakapacker/configuration_spec.rb, test/typescript/securityValidation.test.js
Defines webpack_compile_flags in YAML defaults, Ruby accessor with array-of-strings validation, RBS signature, TypeScript Config interface, and runtime guards in isValidConfig/isPartialConfig, with Ruby and TypeScript test coverage.
Compiler command construction
lib/shakapacker/compiler.rb, spec/shakapacker/compiler_spec.rb
run_webpack now splats a precomputed argv array into Open3.capture3; the new shakapacker_command helper appends -- and webpack_compile_flags when flags are set, with specs covering forwarded flags and no-ruby-runner exec form.
Runner and dev-server passthrough args
lib/shakapacker/runner.rb, lib/shakapacker/dev_server_runner.rb, spec/shakapacker/webpack_runner_spec.rb, spec/shakapacker/dev_server_runner_spec.rb
Runner gains split_passthrough_argv and bundler_argv; option parsing uses pre--- args while post--- args pass through to bundler execution. DevServerRunner mirrors the split, forwards passthrough args into execute_cmd, and checks unsupported switches against bundler_argv.
Docs, changelog, and README updates
CHANGELOG.md, docs/api-reference.md, docs/configuration.md, .agents/bin/README.md
Changelog entry, API reference example, Quick Reference table row, and dedicated configuration section document webpack_compile_flags; agents README is reformatted as a table.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

  • shakacode/shakapacker#1100: Both PRs modify lib/shakapacker/compiler.rb’s compilation failure flow, including spawn error handling and doctor-hint behavior.

Suggested labels

p2

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 21.88% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title accurately summarizes the main changes: a new compile-flags config plus stricter CLI flag handling.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch codex/1175-compile-flags

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@justin808
justin808 marked this pull request as ready for review June 29, 2026 04:20
@greptile-apps

greptile-apps Bot commented Jun 29, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR adds Rails-driven webpack/rspack compile flags to Shakapacker. The main changes are:

  • New webpack_compile_flags config with Ruby validation and defaults.
  • Compiler command forwarding through array argv.
  • Updated TypeScript/RBS config shapes and guards.
  • Documentation, changelog, and focused test coverage.
  • Generator template pin for the Babel React preset.

Confidence Score: 4/5

The TypeScript config guard can reject upgraded configs that omit the new key.

  • Ruby config loading treats a missing webpack_compile_flags value as an empty array.
  • The full TypeScript guard currently rejects the same missing value before defaults are guaranteed to be merged.
  • Compile flag forwarding mostly follows the existing array-argv command style, with a smaller edge case around runner-reserved flags.

package/utils/typeGuards.ts

Important Files Changed

Filename Overview
lib/shakapacker/compiler.rb Builds the Shakapacker compile command as array argv and appends configured compile flags.
lib/shakapacker/configuration.rb Adds Ruby access and validation for webpack_compile_flags, including a missing-key fallback to [].
package/utils/typeGuards.ts Adds TypeScript validation for the new option, but the full-config guard rejects configs where the key is absent.
package/types.ts Adds webpack_compile_flags to the exported TypeScript Config shape.
lib/install/config/shakapacker.yml Adds the default empty compile flags array to the generated Shakapacker config.

Reviews (1): Last reviewed commit: "Fix CI generator dependency drift" | Re-trigger Greptile

Comment thread package/utils/typeGuards.ts
Comment thread lib/shakapacker/compiler.rb Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@CHANGELOG.md`:
- Line 14: The CHANGELOG entry for `webpack_compile_flags` needs to follow the
existing convention by adding the pull request link and author attribution.
Update the entry format in `CHANGELOG.md` so it includes the PR reference and
`by [username](https://github.com/username)` style used by the other entries,
matching the project’s `CHANGELOG` examples.

In `@package/types.ts`:
- Line 24: The Config type is too strict because webpack_compile_flags is
required even though the validation paths allow configs without it. Update the
Config definition in types.ts to make webpack_compile_flags optional, or ensure
the narrowing logic in the relevant validation function(s) defaults it to an
empty array before treating the object as Config.

In `@package/utils/typeGuards.ts`:
- Around line 387-394: The new webpack_compile_flags partial-config validation
is being skipped whenever shouldValidate() is false, so malformed values can
still pass through in production. In isPartialConfig(), move the
webpack_compile_flags array-of-strings check above the strict-validation fast
path so it always runs, and keep the existing type guard behavior consistent for
merged YAML in package/config.ts and any callers of isPartialConfig().

In `@spec/shakapacker/compiler_spec.rb`:
- Around line 42-49: The spec for Shakapacker::Compiler#compile is using an
indirect captured_args variable instead of the preferred RSpec spy pattern.
Update the example to spy on Open3.capture3 directly and assert the call with
have_received, keeping the existing flag expectations for --progress and
--fail-on-warnings. Use the existing compile setup and the Open3.capture3 stub
in compiler_spec.rb as the target for the spy assertion.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 67debecd-b908-41c4-be6e-e01917febbc9

📥 Commits

Reviewing files that changed from the base of the PR and between c65b0a7 and b2bee21.

📒 Files selected for processing (14)
  • .agents/bin/README.md
  • CHANGELOG.md
  • docs/api-reference.md
  • docs/configuration.md
  • lib/install/config/shakapacker.yml
  • lib/shakapacker/compiler.rb
  • lib/shakapacker/configuration.rb
  • package/types.ts
  • package/utils/typeGuards.ts
  • sig/shakapacker/configuration.rbs
  • spec/generator_specs/e2e_template/template.rb
  • spec/shakapacker/compiler_spec.rb
  • spec/shakapacker/configuration_spec.rb
  • test/typescript/securityValidation.test.js

Comment thread CHANGELOG.md Outdated
Comment thread package/types.ts Outdated
Comment thread package/utils/typeGuards.ts Outdated
Comment thread spec/shakapacker/compiler_spec.rb Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
lib/shakapacker/dev_server_runner.rb (1)

68-89: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Block unsupported dev-server flags after --.

These paths now forward post-separator args into bundler_argv, but detect_unsupported_switches! still validates only @argv. That means bin/shakapacker-dev-server -- --host ... --port ... --https bypasses the documented guard, and detect_port! still checks the configured port instead of the one actually sent to the dev server. Please validate bundler_argv here (or strip those switches before constructing the runner).

Also applies to: 312-312

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@lib/shakapacker/dev_server_runner.rb` around lines 68 - 89, The dev-server
flag validation is only checking `@argv`, so switches passed after the separator
can bypass the unsupported-flag guard and the real dev-server port may not be
validated. Update DevServerRunner.run_with_build_config and the runner flow that
builds bundler_argv to inspect the post-`--` arguments as well, and make
detect_unsupported_switches! / detect_port! operate on the actual args forwarded
to the dev server or strip unsupported switches before creating the runner. Use
the existing DevServerRunner, detect_unsupported_switches!, detect_port!, and
bundler_argv path to keep the validation aligned with what is actually launched.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@lib/shakapacker/dev_server_runner.rb`:
- Around line 68-89: The dev-server flag validation is only checking `@argv`, so
switches passed after the separator can bypass the unsupported-flag guard and
the real dev-server port may not be validated. Update
DevServerRunner.run_with_build_config and the runner flow that builds
bundler_argv to inspect the post-`--` arguments as well, and make
detect_unsupported_switches! / detect_port! operate on the actual args forwarded
to the dev server or strip unsupported switches before creating the runner. Use
the existing DevServerRunner, detect_unsupported_switches!, detect_port!, and
bundler_argv path to keep the validation aligned with what is actually launched.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 4407721b-4c9c-4941-90cf-6c3496570d16

📥 Commits

Reviewing files that changed from the base of the PR and between b2bee21 and dfaccbd.

📒 Files selected for processing (10)
  • CHANGELOG.md
  • docs/configuration.md
  • lib/shakapacker/compiler.rb
  • lib/shakapacker/dev_server_runner.rb
  • lib/shakapacker/runner.rb
  • package/types.ts
  • package/utils/typeGuards.ts
  • spec/shakapacker/compiler_spec.rb
  • spec/shakapacker/webpack_runner_spec.rb
  • test/typescript/securityValidation.test.js
✅ Files skipped from review due to trivial changes (1)
  • CHANGELOG.md
🚧 Files skipped from review as they are similar to previous changes (5)
  • package/types.ts
  • spec/shakapacker/compiler_spec.rb
  • package/utils/typeGuards.ts
  • lib/shakapacker/compiler.rb
  • docs/configuration.md

@justin808

Copy link
Copy Markdown
Member Author

@claude review this PR

@github-actions

github-actions Bot commented Jun 29, 2026

Copy link
Copy Markdown

Claude finished @justin808's task in 3m 38s —— View job


PR Review: Add webpack compile flags config

  • Read changed files and gather context
  • Review Ruby implementation (compiler, configuration, runner, dev_server_runner)
  • Review JavaScript/TypeScript implementation (types, typeGuards)
  • Review tests
  • Review documentation and changelog
  • Check open issues from existing review comments
  • Post final review

Overall Assessment

This is a solid, well-scoped PR. The core feature is simple but implemented with care across all the right layers (Ruby config, compile path, TypeScript types, and tests). All previously raised CodeRabbit and Greptile issues have been addressed in subsequent commits. Ready to merge with a couple of minor observations noted below.


Positive Findings

Shell injection prevention (lib/shakapacker/compiler.rb:214-220)
Compile flags are passed as individual array elements to Open3.capture3, not as a shell string. The -- separator correctly isolates them from runner options:

command += ["--", *compile_flags] if compile_flags.any?

The if compile_flags.any? guard also prevents a stray -- when no flags are configured. ✓

Passthrough isolation works correctly
When bin/shakapacker receives ["--", "--progress"] as argv, split_passthrough_argv splits it into runner_argv = [] and passthrough_argv = ["--progress"]. The empty runner_argv means no Shakapacker-specific flags are triggered, and the flags reach webpack via bundler_argv = @argv + @passthrough_argv. The flow is clean.

TypeScript types are correctly optional (package/types.ts:24)
webpack_compile_flags?: string[] — the ? means existing configs without this key will not fail isValidConfig. ✓

Both TypeScript guards validate before the production fast path
In both isValidConfig (line 192-202) and isPartialConfig (line 344-351), the webpack_compile_flags array-of-strings check runs before the shouldValidate() production fast path that short-circuits. This ensures malformed values are always caught, not silently accepted in production. ✓

Dev server passthrough validation (lib/shakapacker/dev_server_runner.rb:265)
detect_unsupported_switches! correctly checks bundler_argv = @argv + @passthrough_argv, so --host / --port after -- are caught and rejected. The new test in dev_server_runner_spec.rb:140-154 confirms this. ✓

Ruby config validation (lib/shakapacker/configuration.rb:239-248)
Clear error message on misconfiguration, returns [] on nil (missing key), so upgraded apps that haven't added the key yet work without changes. ✓

RSpec spy pattern (spec/shakapacker/compiler_spec.rb:44-51)
The compile flags test correctly uses have_received(:capture3) with a block — consistent with the project's preferred spy style. ✓


Minor Observations

1. No negative test for empty flags (lib/shakapacker/compiler.rb:218)
There's a test that flags are forwarded when present, but no test verifying that an empty webpack_compile_flags: [] results in -- NOT being added to the command. Low risk since the guard is simple, but worth adding for regression protection.

2. Scope clarification in shakapacker.yml comment (lib/install/config/shakapacker.yml:39-41)
The current comment:

# Extra command-line flags passed to webpack/rspack when compiling.
# Example: ["--progress", "--fail-on-warnings"]

It's worth noting that these flags apply only to Rails-driven compiles (Shakapacker.compilebin/shakapacker) — not to running bin/shakapacker directly from the CLI. Users running bin/shakapacker --progress directly won't see any interaction with this config. The docs cover this, but the inline comment could save a support question.

3. Potential command subcommand interference
If a user (incorrectly) configures webpack_compile_flags: ["build"] with rspack, the bundler argv filter at runner.rb:292 would see "build" as an rspack command and skip adding --config. This is a misuse rather than a bug, and the documentation examples show appropriate flags, so it's acceptable. Worth a note in docs if not already there.


Status of Previously Raised Issues

Issue Status
CHANGELOG format missing PR link/author ✓ Fixed (dfaccbd) — line 14 includes PR link and author
webpack_compile_flags required in Config type ✓ Fixed — marked optional with ?
isPartialConfig validation bypassed in production ✓ Fixed — check is before shouldValidate() fast path
RSpec indirect counter pattern ✓ Fixed — uses have_received block
Dev server flags bypassing unsupported-switch guard ✓ Fixed (1aac875) — bundler_argv includes passthrough args
Greptile P2: runner flags steal bundler flags False positive — -- separator means compile flags only reach passthrough_argv, which bypasses all Shakapacker runner option parsing

Verdict

The feature is correctly implemented, well-tested, and doesn't introduce any security or correctness issues. The two minor observations above (missing negative test, inline comment clarification) are non-blocking. Happy to merge.

* origin/main:
  Fix doctor Sass implementation detection (#1178)

# Conflicts:
#	spec/generator_specs/e2e_template/template.rb
@claude

claude Bot commented Jun 29, 2026

Copy link
Copy Markdown
Contributor

Review: Add webpack compile flags config

This PR adds a webpack_compile_flags array option to shakapacker.yml that passes extra CLI flags to the webpack/rspack compile command via bin/shakapacker, refactors argument handling across Runner and DevServerRunner to properly split argv on --, and adds matching Ruby config validation, RBS types, and TypeScript type guards. The core design is sound — using an array-only option with Open3.capture3 in argv-array mode avoids shell injection from user-supplied flags.

Three findings worth addressing before merge:

  1. Confirmed bug (compiler_spec.rb) — The expect() calls inside the have_received(:capture3) block are silently swallowed by RSpec's argument-matcher machinery. When ExpectationNotMetError is raised inside the block, MessageExpectation#received_message_matching_args_and_block? catches it and returns false (treating it as a non-match) rather than propagating it as a test failure. The test can fail with a confusing "received with unexpected arguments" message, and under some conditions may not fail even when the -- separator is missing. See inline comments.

  2. Plausible regression (compiler.rb) — When bin/shakapacker is not a Ruby script (optional_ruby_runner returns "") and webpack_compile_flags is empty, shakapacker_command returns a single-element array. Splatted into Open3.capture3, that becomes a bare unquoted string dispatched through the shell — regressing from the old code which single-quoted the path. A Rails app root containing spaces breaks in this case. See inline comment.

  3. Plausible inconsistency (typeGuards.ts) — The webpack_compile_flags check in isPartialConfig was placed before the shouldValidate() early-return, making it always run in production. Unlike the path-traversal checks above that gate (which carry explicit "SECURITY: ALWAYS runs" comments), this check has no such justification. See inline comment.

Comment thread spec/shakapacker/compiler_spec.rb Outdated
Comment thread lib/shakapacker/compiler.rb Outdated
Comment thread package/utils/typeGuards.ts

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@spec/shakapacker/dev_server_runner_spec.rb`:
- Around line 146-151: The spec for Shakapacker::DevServerRunner is using a
constructor input that bypasses the separator-splitting behavior, so it does not
actually verify post-`--` handling. Update the example around
detect_unsupported_switches! to build the runner from the split bundler_argv and
passthrough argv state (or cover the public run path) so the assertion depends
on the separator forwarding logic and will fail if `--host` is not preserved
after `--`.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: ab3ba05e-a639-492c-b4f1-dae5ff780822

📥 Commits

Reviewing files that changed from the base of the PR and between dfaccbd and d9700a3.

📒 Files selected for processing (6)
  • CHANGELOG.md
  • lib/shakapacker/compiler.rb
  • lib/shakapacker/dev_server_runner.rb
  • package/utils/typeGuards.ts
  • spec/shakapacker/compiler_spec.rb
  • spec/shakapacker/dev_server_runner_spec.rb
✅ Files skipped from review due to trivial changes (2)
  • CHANGELOG.md
  • package/utils/typeGuards.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • lib/shakapacker/compiler.rb
  • lib/shakapacker/dev_server_runner.rb

Comment thread spec/shakapacker/dev_server_runner_spec.rb Outdated
Comment thread lib/shakapacker/runner.rb Outdated
Comment thread lib/shakapacker/runner.rb Outdated
@claude

claude Bot commented Jun 29, 2026

Copy link
Copy Markdown
Contributor

Review: Add webpack compile flags config

This PR adds a webpack_compile_flags config option that passes extra CLI arguments to webpack/rspack during Rails-driven compiles. The core approach is sound: flags are validated as an array of strings, passed as a proper argv array to Open3.capture3 (avoiding shell expansion), forwarded through bin/shakapacker via a -- separator, and split back off in Runner.run before reaching the bundler. The -- passthrough refactor in Runner/DevServerRunner is a clean improvement.

Two small issues found:

1. Dead guard in split_passthrough_argv (lib/shakapacker/runner.rb:41)
argv[(separator_index + 1)..] || []Array#[] with an endless range never returns nil, so the || [] is unreachable. Inline comment left.

2. if passthrough_argv truthy check is fragile (lib/shakapacker/runner.rb:236)
The intent is to distinguish nil (needs auto-splitting) from an already-split result. An empty [] is truthy in Ruby, so both are handled correctly at all current call sites. But if passthrough_argv: [] is ever passed alongside an argv that still contains --, the separator leaks into @argv and reaches the bundler. !passthrough_argv.nil? makes the invariant explicit. Inline comment left.

One test gap worth noting:
spec/shakapacker/compiler_spec.rb has a test for passing compile flags that does not stub optional_ruby_runner, so whether the [runner, *command] branch (non-empty ruby runner + non-empty flags) is exercised depends on the shebang in the test app's bin/shakapacker. A test that explicitly stubs optional_ruby_runner to RbConfig.ruby and asserts the command starts with the ruby path would make that coverage deterministic.

@claude

claude Bot commented Jun 29, 2026

Copy link
Copy Markdown
Contributor

Review: Add webpack compile flags config

This PR adds two complementary mechanisms for forwarding extra flags to webpack/rspack during Rails-driven compiles: a webpack_compile_flags YAML config key (for static, per-environment flags like --fail-on-warnings) and a -- passthrough separator in the runner (for ad-hoc CLI forwarding). The Open3 call is correctly migrated to array form, eliminating shell-quoting risks. Four issues worth addressing follow.


Bug — passthrough --host/--port silently blocked in dev-server

detect_unsupported_switches! checks bundler_argv which is @argv + @passthrough_argv, so --host or --port flags passed via -- --host 0.0.0.0 end up in @passthrough_argv, get merged in, and trigger the hard exit with "The following CLI switches are not supported by Shakapacker." The PR even has a test that asserts this happens ("rejects unsupported dev-server flags after --"), but the message is confusing: the user explicitly used -- to bypass Shakapacker's handling, yet they get the same error as someone who forgot to use YAML config.

If the intent is to keep --host/--port unsupported even via passthrough (because Shakapacker reads them from config and ignores the CLI values), the error message should say so. If they should be forwarded, the check needs to look at @argv only, not bundler_argv.


Inconsistency — isPartialConfig validates webpack_compile_flags outside the production fast path

In typeGuards.ts, isPartialConfig validates webpack_compile_flags before the if (!shouldValidate()) { return true } guard, while every other optional-field check (strings, booleans, additional_paths) sits after it. In production without SHAKAPACKER_STRICT_VALIDATION, this means a malformed webpack_compile_flags correctly returns false, but a malformed source_path: 123 silently passes. The comment explains the intent ("CONFIG CONTRACT"), but the structural asymmetry will surprise the next contributor who adds a field and follows the established pattern.

Consider either documenting this as a deliberate security-surface exception at the top of the function, or moving the check into a separate pre-validation layer that runs before shouldValidate() for all fields that need it.


Cleanup — duplicated array-of-strings check in typeGuards.ts

The webpack_compile_flags validation predicate (!Array.isArray(...) || !...every(flag => typeof flag === 'string')) is written out identically in both isValidConfig (lines 193–202) and isPartialConfig (lines 348–353). A one-liner helper such as isStringArray(v: unknown): boolean would remove the duplication and make the intent obvious at each call site.


Minor — dead code: || [] guard in split_passthrough_argv

argv[(separator_index + 1)..] || [] — the || [] is never reached. In Ruby 2.6+, Array#[] with an endless range (..) always returns an Array (empty array if the index is past the end); it never returns nil. Safe to remove.

Comment thread lib/shakapacker/dev_server_runner.rb
Comment thread package/utils/typeGuards.ts Outdated
Comment thread package/utils/typeGuards.ts
@claude

claude Bot commented Jun 30, 2026

Copy link
Copy Markdown
Contributor

Review

This PR adds webpack_compile_flags as a validated array-of-strings option in shakapacker.yml, refactors CLI -- passthrough arg splitting across all runners, and tightens dev-server flag validation. The overall design is solid — using array-form Open3.capture3 eliminates the shell-injection risk of the old string-interpolated command, and the SpawnFailure sentinel cleanly separates "process could not start" from "process ran and failed." Four issues worth addressing before merge:

1. --help=<variant> passes webpack_compile_flags validation (bug)

SHAKAPACKER_RUNNER_COMMANDS blocks "--help=verbose" as an exact string, but webpack accepts --help=compact, --help=detailed, etc. Any --help=<other> passes valid_webpack_compile_flag? and gets forwarded after -- to bin/shakapacker, which causes webpack to print help and exit instead of compiling assets. The same gap exists in package/utils/typeGuards.ts. The watch-flag fix (SHAKAPACKER_WATCH_FLAG_PATTERN) shows the right pattern — a SHAKAPACKER_HELP_FLAG_PATTERN = /\A(?:--help|-h)(?:=.*)?\z/ would cover all variants. (Inline comment on configuration.rb:32.)

2. assets_bundler_commands in RSPACK_RUNNER_EXTENSION is dead code (cleanup / latent correctness issue)

The extension defines both assets_bundler_commands and config_incompatible_args. Because config_incompatible_args is overridden in the same extension and doesn't call assets_bundler_commands, the latter is never invoked when the extension is applied. The base-class config_incompatible_args calls it, but that method is shadowed. A cleaner design: remove config_incompatible_args from the extension and only override assets_bundler_commands — the base-class implementation then picks it up and the passthrough-build/watch asymmetry goes away too. (Inline comment on runner.rb:37.)

3. Uncaught Errno::ENOEXEC / Errno::EPERM in run_webpack (robustness)

The new rescue only catches EACCES and ENOENT. Errno::ENOEXEC (binstub with Windows CRLF on Linux, wrong interpreter, etc.) and Errno::EPERM (SELinux/AppArmor denial) escape the block, so after_compile_hook gets called despite no compilation running and the caller sees an unhandled exception instead of a graceful false. Adding both to the rescue clause would close this. (Inline comment on compiler.rb:200.)

4. Duplicate flag lists in Ruby and TypeScript will drift (maintainability)

SHAKAPACKER_NODE_FLAGS, SHAKAPACKER_RUNNER_COMMANDS, and SHAKAPACKER_WATCH_FLAGS are re-declared verbatim in typeGuards.ts with only a "keep in sync" comment as the guard. A new runner flag added on the Ruby side (Rails validates on startup) will be silently accepted by isWebpackCompileFlags until someone notices. Worth either extracting to a shared JSON source-of-truth, or considering whether the TS-side flag validation adds enough value over the Rails-startup check to justify the maintenance cost. (Inline comment on typeGuards.ts:65.)

@claude

claude Bot commented Jun 30, 2026

Copy link
Copy Markdown
Contributor

Review findings — see inline comments for details. Summary: (1) webpack_compile_flags empty array in the install template is unnecessary noise since the code already handles the nil case. (2) RspackRunner.run bypasses RSPACK_RUNNER_EXTENSION so config_incompatible_args for build/watch is not applied on the direct subclass path. (3) The validation error message says 'webpack' even for rspack projects. (4) PR scope wider than the feature per CLAUDE.md.

Comment thread lib/install/config/shakapacker.yml
Comment thread lib/shakapacker/configuration.rb
Comment thread lib/shakapacker/runner.rb
@claude

claude Bot commented Jun 30, 2026

Copy link
Copy Markdown
Contributor

Review: Add compile flags config and tighten CLI flag handling

This PR adds webpack_compile_flags as a YAML config option and threads it through Shakapacker::Compiler using safe array-form Open3.capture3, while also splitting the -- passthrough separator throughout the runner stack so Shakapacker-owned flags cannot leak into bundler argv. The overall approach is solid and the test coverage is extensive.

Three issues worth addressing before merge:


1. RspackRunner.run is missing the rspack-specific config_incompatible_args (bug)

RSPACK_RUNNER_EXTENSION defines a config_incompatible_args override that correctly suppresses --config injection for build/watch commands. However, RspackRunner.run calls new(argv).run directly, creating a RspackRunner instance that inherits the base Runner#config_incompatible_args (which only checks BASE_COMMANDS — no build or watch). Only the Runner.run code path applies RSPACK_RUNNER_EXTENSION via extend.

Concretely: if someone calls Shakapacker::RspackRunner.run(["build"]) (a public API), config_incompatible_args returns [] and --config is incorrectly injected. The rspack_runner_spec.rb tests that rely on verify_command (which uses RspackRunner.new) exercise this broken path. The new test at spec line 221 avoids the issue by using Shakapacker::Runner.new and manually inlining the override rather than using RSPACK_RUNNER_EXTENSION.

The simplest fix is to override config_incompatible_args in RspackRunner itself, or have RspackRunner.run extend the instance with RSPACK_RUNNER_EXTENSION.


2. TypeScript flag lists are manually duplicated from Ruby (maintenance risk)

package/utils/typeGuards.ts copies every constant from Shakapacker::Configuration verbatim (SHAKAPACKER_NODE_FLAGS, SHAKAPACKER_RUNNER_COMMANDS, SHAKAPACKER_WATCH_FLAGS, SHAKAPACKER_MANAGED_COMPILE_FLAGS, and their regex patterns) with a "Keep in sync with..." comment. When a flag is added to the Ruby constants but not the TS copy (or vice versa), Ruby and TypeScript validation silently diverge — and no existing test will catch it because the test suite covers each side independently. Consider generating the TS constants at build time from the Ruby source, or at minimum add a cross-language sync test.


3. exit! vs exit inconsistency in exit_after_shakapacker_flag_error

DevServerRunner#exit_after_shakapacker_flag_error calls exit!(status), which bypasses at_exit handlers. Runner#exit_after_shakapacker_flag_error calls exit(status), which runs them. Additionally, detect_unsupported_switches! in DevServerRunner calls exit! directly (lines 280, 285) without going through exit_after_shakapacker_flag_error, so those paths don't get the log_output.flush added by this PR. The inconsistency is low risk today (no registered at_exit handlers, $stdout is synced), but it's worth standardising — either always use the helper or document the deliberate difference.

Comment thread lib/shakapacker/runner.rb
Comment thread package/utils/typeGuards.ts
Comment thread lib/shakapacker/dev_server_runner.rb
@justin808

justin808 commented Jun 30, 2026

Copy link
Copy Markdown
Member Author

Addressed Claude review threads across the 4cc01b3, 5d77111, 1d06f70, and 6608269 review passes.

Fixed:

  • Restored the explicit webpack_compile_flags: [] default after review clarified config-inspection consistency; the generated template now has the key plus opt-in docs.
  • Made the compile flag validation message bundler-neutral.
  • Removed the rspack config-incompatible special case and added coverage that rspack build preserves --config.
  • Routed unsupported dev-server flag errors through the normal flushed exit(status) path and removed the redundant dev-server override.
  • Added keep-in-sync comments for the TypeScript copies of the Ruby flag constants and related patterns.
  • Extracted a reusable WEBPACK_RUNNER_EXTENSION constant to match the existing rspack runner extension pattern.

Declined with evidence:

  • Re-adding rspack build/watch as config-incompatible commands. Installed rspack 2.0.1 accepts --config on the default/build path, and tests now assert direct and top-level rspack build preserve --config.
  • Restricting config-incompatible detection to @argv. Passthrough args after -- are bundler-owned, so bundler help/version/info words there should still suppress automatic --config injection.

Deferred:

  • [auto-deferred] A shared Ruby/TypeScript generated manifest or CI sync script remains outside this PR because it widens the package/gem layout and build-time contract. The current PR keeps the validators covered by targeted tests and sync comments.

Skipped: none.

@claude

claude Bot commented Jun 30, 2026

Copy link
Copy Markdown
Contributor

Review: Add compile flags config and tighten CLI flag handling

This PR adds a webpack_compile_flags config option so Rails-driven compiles can pass extra CLI flags to webpack/rspack, splits passthrough -- argv throughout the runner hierarchy, switches Open3.capture3 in the compiler to array argv (eliminating the shell-interpolated command string), and adds validation in both Ruby and TypeScript.

The approach is well-structured and the test coverage is thorough. Three issues worth addressing before merge:


1. Missing YAML key in the install template

lib/install/config/shakapacker.yml adds only a comment block with no actual webpack_compile_flags: key. Every other option in the defaults section has an explicit key (even optional ones appear as commented-out keys like # precompile_hook: ~). A bare comment is invisible to config-inspection tooling and inconsistent with the rest of the file.

2. Unnecessary override in DevServerRunner

DevServerRunner defines exit_after_shakapacker_flag_error privately (line 347) with a body identical to Runner's protected implementation. DevServerRunner < Runner, so the child inherits the method — the override is dead weight that will silently diverge if the parent changes.

3. Flag constant duplication Ruby → TypeScript

All five flag arrays (SHAKAPACKER_NODE_FLAGS, SHAKAPACKER_RUNNER_COMMANDS, SHAKAPACKER_WATCH_FLAGS, etc.) are copy-pasted into package/utils/typeGuards.ts with only SHAKAPACKER_NODE_FLAGS carrying a "Keep in sync" comment. A contributor who adds a new managed flag in configuration.rb can easily miss the TypeScript side, causing isWebpackCompileFlags to silently pass flags that Ruby rejects. At minimum, add "Keep in sync with Shakapacker::Configuration::" comments to the other four constants.

Comment thread lib/install/config/shakapacker.yml
Comment thread lib/shakapacker/dev_server_runner.rb Outdated
Comment thread package/utils/typeGuards.ts
Comment thread lib/shakapacker/runner.rb
@claude

claude Bot commented Jun 30, 2026

Copy link
Copy Markdown
Contributor

Review

This PR adds webpack_compile_flags as a YAML array option, threads the flags through Compiler#run_webpack via array-form Open3.capture3 (eliminating the old shell-string path), and tightens -- passthrough handling across Runner and DevServerRunner. The validation layer (Ruby + TypeScript mirrors) is thorough and the test coverage is extensive.

Findings

Webpack runner extension inconsistency (see inline comment on runner.rb:32-37)

The rspack path was correctly extracted to the named RSPACK_RUNNER_EXTENSION constant, but the webpack path still creates an anonymous Module.new on every Runner.run and run_with_build_config call. Each anonymous module is attached to the singleton class and lingers until that class is GC-collected. Extracting a WEBPACK_RUNNER_EXTENSION constant symmetrically with rspack removes the per-call allocation and the inconsistency.

API behaviour change in Compiler#compile on spawn failure

Previously, ENOENT/EACCES from optional_ruby_runner propagated as unhandled exceptions out of compile. Now they are caught, logged, and compile returns false. Existing callers (Manifest, commands.rb) check the return value rather than rescuing, so they are unaffected - but any app-level code that rescued exceptions from Shakapacker.compile will silently stop receiving them. Worth a note in the CHANGELOG or upgrade guide as a quiet API contract change.

PR scope

The compile-flags feature itself is solid. The -- passthrough separator refactor, the new --trace-deprecation/--no-deprecation dev-server flags, and the --host/--port blocking-after--- blocking change are each independent enough to warrant their own PRs per the project focus convention. The broad scope makes selective revert harder if any one piece causes problems in the field.

@claude

claude Bot commented Jun 30, 2026

Copy link
Copy Markdown
Contributor

Review Summary

This PR adds a webpack_compile_flags YAML option and reworks the -- passthrough-separator flow across Runner and DevServerRunner. The compile path is meaningfully improved (array-form Open3, spawn error handling), the validation layers are thorough, and test coverage is solid. Two correctness bugs are worth fixing before merge.

Bug 1 (blocking) — RSPACK_RUNNER_EXTENSION drops assets_bundler_commands

The old inline anonymous module used for rspack overrode both build_cmd and assets_bundler_commands (returning BASE_COMMANDS + %w[build watch]). The new RSPACK_RUNNER_EXTENSION constant only defines build_cmd. Neither RspackRunner nor the base Runner class separately defines this override.

Result: when a user runs bin/shakapacker build (rspack subcommand syntax), config_incompatible_args returns [] because "build" is not in BASE_COMMANDS, so --config rspack.config.js is injected before build, breaking rspack's CLI subcommand parsing.

Fix — restore the assets_bundler_commands override in RSPACK_RUNNER_EXTENSION:

RSPACK_RUNNER_EXTENSION = Module.new do
  def build_cmd
    package_json.manager.native_exec_command("rspack")
  end

  def assets_bundler_commands
    BASE_COMMANDS + %w[build watch]
  end
end

Bug 2 (minor) — config_incompatible_args intersects all of bundler_argv including passthrough

config_incompatible_args computes bundler_argv & assets_bundler_commands, and bundler_argv is @argv + @passthrough_argv. This means a BASE_COMMANDS word appearing after -- (e.g., bin/shakapacker -- version) will suppress --config injection. In practice those words are valid bundler subcommands so the suppression is coincidentally correct, but the intersection should only check @argv (the runner-owned side) for subcommand detection — passthrough words are bundler arguments and shouldn't influence Shakapacker's config-file decision.

Observation — TypeScript flag lists require manual sync with Ruby

The four lists in package/utils/typeGuards.ts (SHAKAPACKER_NODE_FLAGS, SHAKAPACKER_RUNNER_COMMANDS, SHAKAPACKER_WATCH_FLAGS, SHAKAPACKER_MANAGED_COMPILE_FLAGS) are copy-pasted from Ruby constants with "Keep in sync" comments. If a flag is added on the Ruby side without a matching TS update, the TS guard silently accepts it while the Ruby side rejects it at runtime. Consider generating these constants from a shared JSON file at build/install time, or at minimum adding a CI check that compares the two sources.

Comment thread lib/shakapacker/runner.rb
Comment thread lib/shakapacker/runner.rb
Comment thread package/utils/typeGuards.ts
@justin808
justin808 merged commit 81b5634 into main Jun 30, 2026
34 checks passed
@justin808
justin808 deleted the codex/1175-compile-flags branch June 30, 2026 09:56
justin808 added a commit that referenced this pull request Jul 4, 2026
## Summary

Stamps the **`v10.2.0`** release section in `CHANGELOG.md` and adds the
user-visible entries that were still missing for PRs merged since
`v10.1.0`.

Header format matches the repo convention (`## [v10.2.0] - July 3,
2026`) and is parseable by `rakelib/release.rake`'s
`extract_changelog_section` (`## [v<npm-version>]`), so `bundle exec
rake release` / `sync_github_release` will pick up the notes
automatically.

### Changelog changes

- **Version header**: inserted `## [v10.2.0] - July 3, 2026` immediately
after `## [Unreleased]`; all accumulated entries now live under it, and
`## [Unreleased]` is empty.
- **Compare links**: `[unreleased]` now compares `v10.2.0...main`; added
`[v10.2.0]: …/compare/v10.1.0...v10.2.0`.

### New entries added (were missing)

| PR | Section | Note |
| --- | --- | --- |
| [#1187](#1187) | Added |
Babel 8 peer dependency support + preset option compatibility |
| [#1184](#1184) | Added |
Folded into the #695 AI-prompt entry (gates the React on Rails section
on app detection) |
| [#1142](#1142) | Fixed |
Rspack dev-server config no longer loads in static watch mode (fixes
#1137) |

### Already documented (carried into v10.2.0)

`#1180`, `#695`, `#1141`, `#1150`, `#1179`, `#1192`, `#1127`, `#1178`,
`#1161`, `#1147`.

### Reviewed and intentionally excluded (not user-visible)

Docs: `#1145`, `#1148`, `#1152`, `#1155`, `#1183`, `#1188`, `#1189`,
`#1193`.
CI: `#1151`, `#1168`, `#1171`.
Tests / fixtures: `#1128`, `#1154`, `#1167`, `#1186`.
Workflow / agent tooling: `#1153`, `#1176`, `#1182`.

(`#1107` is already documented under `## [v10.1.0]`.)

## Next step

After merge, run the repo's release task (no args) — it reads `v10.2.0`
from the changelog and creates the GitHub release from this section.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Ability to set Webpack flags when called via rails assets:precompile

1 participant