Skip to content

Tighten helper binstub parity and PATH lookup#1128

Merged
justin808 merged 10 commits into
mainfrom
jg/1123-binstub-sync-coverage
May 29, 2026
Merged

Tighten helper binstub parity and PATH lookup#1128
justin808 merged 10 commits into
mainfrom
jg/1123-binstub-sync-coverage

Conversation

@justin808

@justin808 justin808 commented May 21, 2026

Copy link
Copy Markdown
Member

Summary

Adds focused parity coverage for the helper binstub contract introduced in #1123.

The PR keeps the checked-in Ruby helper wrappers, the dummy app wrapper, and the createBinStub TypeScript template aligned so future helper drift fails CI quickly.

Changes

  • Export createBinStub from package/configExporter/cli.ts for test use.
  • Add test/configExporter/createBinStub.test.js, which generates shakapacker-config and diff-bundler-config and byte-compares them with lib/install/bin/*.
  • Assert generated helper binstubs are executable.
  • Keep mkdirSync and chmodSync in the top-level fs import instead of using inline require("fs").
  • Improve spec/shakapacker/binstub_sync_spec.rb failure guidance so contributors are told to update all synchronized copies.

Review follow-up

Addressed the current actionable review threads:

  • Switched to flat test.each(["shakapacker-config", "diff-bundler-config"]).
  • Removed the dead if (tmp) cleanup guard.
  • Added a note explaining why diff-bundler-config is covered even though production init only generates shakapacker-config.
  • Added executable-permission coverage for generated binstubs.
  • Expanded the Ruby sync failure message to name all synchronized copies.
  • Moved mkdirSync and chmodSync into the module-level fs import.
  • Fixed leading and trailing empty PATH segment handling so helper binstubs honor the current directory during Node lookup, and added RSpec coverage for both helper commands.

Discussion/advice:

  • The generated-user-binstub comment concern is intentionally left as discussion-only for this PR. This parity test is meant to prove the generated template matches the checked-in install files byte-for-byte; changing generated comments separately would weaken that contract.
  • Empty PATH segment handling turned out to be a real edge case: Ruby File.join("", "node") checks /node, not the current directory, and Ruby String#split drops trailing empty fields unless called with a negative limit. The helper now preserves trailing entries and maps empty segments to Dir.pwd before joining, preserving normal PATH semantics even though the binstub later chdirs to the app root.

CI note

This branch is rebased onto current origin/main.

The earlier Test Both Bundlers failure was Bundler frozen-mode lockfile drift in spec/dummy/Gemfile.lock: the lockfile still recorded shakapacker (10.1.0.rc.1) while the current gemspec resolves to 10.1.0. That lockfile fix is now already included in current origin/main, and this branch has been rebased on top of it, so the PR diff no longer carries a lockfile change.

The previous claude-review failure was due to the external Claude weekly limit, not the repository diff.

Test plan

  • bundle exec rspec spec/shakapacker/helper_binstubs_spec.rb:211 --format documentation - 4 empty-PATH examples pass for leading and trailing entries across both helper commands
  • bundle exec rspec spec/shakapacker/binstub_sync_spec.rb spec/shakapacker/helper_binstubs_spec.rb - 21 examples, 0 failures
  • yarn jest test/configExporter/createBinStub.test.js - 2 tests passed
  • yarn eslint package/configExporter/cli.ts test/configExporter/createBinStub.test.js
  • bundle exec rubocop spec/shakapacker/helper_binstubs_spec.rb spec/shakapacker/binstub_sync_spec.rb - no offenses
  • git diff --check origin/main...HEAD

Refs #1123.


Note

Low Risk
Changes are confined to install/helper binstub templates and test tooling; behavior fix is narrow (PATH parsing) with new regression coverage.

Overview
Tightens helper binstub parity and fixes Node lookup when PATH contains empty segments (leading/trailing :).

PATH lookup: shakapacker_find_executable now splits PATH with a negative limit so trailing empty entries are kept, and treats an empty segment as Dir.pwd instead of joining to /node. The same Ruby logic is updated in lib/install/bin/*, spec/dummy/bin/shakapacker-config, and the createBinStub template in package/configExporter/cli.ts.

Parity & CI: createBinStub is exported (test-only) and uses top-level fs imports; test/configExporter/createBinStub.test.js byte-compares generated shakapacker-config / diff-bundler-config stubs to lib/install/bin/* and checks execute bits. binstub_sync_spec failure text lists all four sync targets. helper_binstubs_spec adds RSpec for leading/trailing empty PATH entries, refactors real_node_path, and wraps some cases in Bundler.with_unbundled_env.

Reviewed by Cursor Bugbot for commit 88a44c9. Bugbot is set up for automated code reviews on this repo. Configure here.

@coderabbitai

coderabbitai Bot commented May 21, 2026

Copy link
Copy Markdown

Walkthrough

createBinStub is exported from the TypeScript config exporter and fs imports were adjusted. A new Jest test verifies generated binstubs match the checked-in install templates byte-for-byte and have executable permissions.

Changes

Template Parity Testing

Layer / File(s) Summary
Export createBinStub for testing
package/configExporter/cli.ts
Reformat fs destructuring import to include chmodSync and mkdirSync; add a test-oriented comment and export createBinStub for external verification.
Template parity test
test/configExporter/createBinStub.test.js, spec/shakapacker/binstub_sync_spec.rb
Add Jest tests that generate binstubs for shakapacker-config and diff-bundler-config, compare them byte-for-byte to lib/install/bin/*, assert executable bit, and expand an existing RSpec failure message to reference the createBinStub template.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Poem

🐰 I hopped through files to make templates sing,
Exported a stub and taught tests to bring
Byte-for-byte truth, with a chmod cheer,
Binstubs aligned — the meadow's clear! 🥕

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ⚠️ Warning The PR title 'Tighten helper binstub parity and PATH lookup' is partially related to the changeset but mentions 'PATH lookup' which is not present in the actual changes, making it somewhat misleading. Consider revising the title to 'Tighten helper binstub parity coverage' to accurately reflect the actual changes, removing the 'PATH lookup' reference which is not addressed in this PR.
✅ Passed checks (3 passed)
Check name Status Explanation
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.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch jg/1123-binstub-sync-coverage

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 and usage tips.

@greptile-apps

greptile-apps Bot commented May 21, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR tightens the four-way binstub sync contract by adding new test coverage across both the Ruby and JavaScript layers and updating the "keep in sync" comment header in all four copies to list every tracked file.

  • New Ruby sync test (binstub_sync_spec.rb): asserts spec/dummy/bin/shakapacker-config is byte-identical to lib/install/bin/shakapacker-config, catching future drift in the dummy app's wrapper.
  • New JS template test (createBinStub.test.js): invokes the now-exported createBinStub function for both helper names and compares the generated output against the installed lib/install/bin/* files, closing the previously untestable JS↔Ruby template-drift gap.
  • helper_binstubs_spec.rb: adds the previously-missing expect(stderr).to include(\"[Shakapacker] No Gemfile found at\") assertion to the Gemfile-fallback test.

Confidence Score: 4/5

Safe to merge; all changes are test and comment improvements with no production logic modified.

The only changed production code is exporting createBinStub from cli.ts, which is a purely additive change. All other edits are comment text and spec additions. The failure message in the new binstub_sync_spec test is slightly incomplete (omits lib/install/bin/diff-bundler-config from its update instructions), but the JS test and the four-file header comments provide independent backstops.

spec/shakapacker/binstub_sync_spec.rb — the failure message for the new dummy-sync example could be more complete.

Important Files Changed

Filename Overview
lib/install/bin/diff-bundler-config Updated the "keep in sync" comment header to list all four copies; no logic changes.
lib/install/bin/shakapacker-config Updated the "keep in sync" comment header to list all four copies; no logic changes.
package/configExporter/cli.ts Exports createBinStub (previously unexported) and updates the inline comment header; ts-jest transforms the module correctly for the new JS test.
spec/dummy/bin/shakapacker-config Updated comment header to match the other three copies; stays byte-identical to lib/install/bin/shakapacker-config as the new sync test requires.
spec/shakapacker/binstub_sync_spec.rb Adds a byte-identical check for spec/dummy/bin/shakapacker-config and renames the divergent-binstubs example; failure message for the new test omits lib/install/bin/diff-bundler-config from its update instructions.
spec/shakapacker/helper_binstubs_spec.rb Adds a missing expect(stderr).to include("[Shakapacker] No Gemfile found at") assertion to the fallback test; straightforward and correct.
test/configExporter/createBinStub.test.js New Jest test that invokes the exported createBinStub for both helper names and asserts byte-identical output against the installed lib/install/bin/* files; closes the JS↔Ruby template-drift gap.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A["lib/install/bin/shakapacker-config"] <-->|byte-identical\nbinstub_sync_spec.rb| B["spec/dummy/bin/shakapacker-config"]
    A <-->|byte-identical\ncreateB inStub.test.js| C["createBinStub('shakapacker-config')"]
    D["lib/install/bin/diff-bundler-config"] <-->|byte-identical\ncreateB inStub.test.js| E["createBinStub('diff-bundler-config')"]
    C & E -->|generated by| F["package/configExporter/cli.ts\nexport function createBinStub"]
    G["helper_binstubs_spec.rb"] -->|exercises at runtime| A
    G -->|exercises at runtime| D
Loading

Reviews (1): Last reviewed commit: "Tighten binstub sync coverage and helper..." | Re-trigger Greptile

Comment thread spec/shakapacker/binstub_sync_spec.rb Outdated
Comment thread package/configExporter/cli.ts Outdated
Comment thread spec/shakapacker/binstub_sync_spec.rb Outdated
Comment thread test/configExporter/createBinStub.test.js Outdated
@claude

claude Bot commented May 21, 2026

Copy link
Copy Markdown
Contributor

Code Review

Overall: Clean, well-scoped PR that closes real test-coverage gaps. Low risk — no runtime behavior changes. A few minor wording issues worth fixing before merge.

What's good

  • The four-file sync header is now consistent across all copies — the new sync spec will catch future drift immediately.
  • Exporting createBinStub solely for testability is a minimal, non-invasive change.
  • The Jest test covers the one gap the Ruby spec could not reach (JS template vs installed files).
  • The missing-Gemfile warning assertion in helper_binstubs_spec.rb is exactly the kind of behavioral assertion that prevents silent regressions.
  • Renaming the divergent-binstub example is accurate and clearer.

Issues (inline comments posted)

  1. cli.ts comment mislabels the test path — says "spec/binstub_sync_spec.rb's JS sibling" but the test is at test/configExporter/createBinStub.test.js.
  2. Error message in new spec undercounts the copies — says "both files" when there are four; a contributor hitting this failure would miss lib/install/bin/diff-bundler-config.
  3. test.each nesting — nested single-element arrays is the multi-column table pattern; the flat form is more idiomatic for a single parameter.

None of these affect correctness. Issue 2 (error message wording) is the most worth fixing since a contributor would read it under pressure.

@justin808 justin808 force-pushed the jg/1123-binstub-sync-coverage branch from 27a0e53 to e1af730 Compare May 23, 2026 08:59
justin808 added a commit that referenced this pull request May 23, 2026
Address the low-priority polish item from the PR #1104 review.

`shakapacker_node_binary` previously verified Node was installed by
running `system("node", "--version")`, which forks a subprocess on
every binstub invocation just to confirm presence. Replace with a
direct ENV["PATH"] walk, falling back to PATHEXT-style extensions on
Windows. Cuts a fork+exec from every `bin/shakapacker-config` and
`bin/diff-bundler-config` call.

Applied identically across the three checked-in helper binstubs
(lib/install/bin/shakapacker-config, lib/install/bin/diff-bundler-config,
spec/dummy/bin/shakapacker-config) and the createBinStub template in
package/configExporter/cli.ts. binstub_sync_spec.rb +
test/configExporter/createBinStub.test.js (from PR #1128) keep the four
copies byte-aligned.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Comment thread package/configExporter/cli.ts Outdated
Comment thread spec/shakapacker/binstub_sync_spec.rb Outdated
justin808 added a commit that referenced this pull request May 24, 2026
## Summary

Address the **low-priority optimization** from the [PR #1104 follow-up
review](#1123).

This is **PR 3 of 3** for #1123 — the smallest of the three, isolated so
it can land or be punted independently.

### Change

`shakapacker_node_binary` in the helper binstubs previously verified
Node was installed by running `system("node", "--version")` with both
streams piped to `/dev/null`. That forks a subprocess on every binstub
invocation — just to learn whether Node exists. Replaced with a direct
walk of `ENV["PATH"]` that also probes `PATHEXT`-style extensions on
Windows (so `node.exe`/`node.cmd` are still discoverable). One fork+exec
saved per `bin/shakapacker-config` and `bin/diff-bundler-config` call.

The return value is unchanged (the literal string `"node"`), so the
subsequent `exec node_bin, script_path, *ARGV` still goes through the
OS's `PATH` resolution exactly as before.

Applied byte-identically across all four copies kept in sync by PR #1128
(`binstub_sync_spec.rb` + `test/configExporter/createBinStub.test.js`):

- `lib/install/bin/shakapacker-config`
- `lib/install/bin/diff-bundler-config`
- `spec/dummy/bin/shakapacker-config`
- the `createBinStub` template in `package/configExporter/cli.ts`

### Stacked on PR #1128

This branch is stacked on top of `jg/1123-binstub-sync-coverage` (PR
#1128). GitHub will auto-update the base to `main` once #1128 merges. If
#1128 is closed instead, this PR can rebase onto `main` with a trivial
change (only the comment header and `NODE_ENV` line differ, and only the
comment-header lines need replaying).

Refs #1123.

## Test plan

- [x] `bundle exec rspec spec/shakapacker/binstub_sync_spec.rb` — 2
examples pass (dummy↔install parity preserved)
- [x] `bundle exec rspec spec/shakapacker/helper_binstubs_spec.rb` — 8
examples pass, including the "node unavailable" path that uses
`PATH=/nonexistent`
- [x] `yarn jest test/configExporter/createBinStub.test.js` — 2 examples
pass (template still matches install templates byte-for-byte)
- [x] `bundle exec rubocop` — no offenses on changed Ruby files
- [x] `yarn eslint` — no errors on changed TS file
- [x] `ruby -c` on all three modified binstubs — syntax OK

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

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **Medium Risk**
> Touches the helper binstubs used to launch Node-based tooling; any bug
in the new `PATH`/`PATHEXT` scan could prevent commands from running,
especially on Windows.
> 
> **Overview**
> Replaces `shakapacker_node_binary`’s `system("node", "--version")`
check with a no-fork scan of `ENV["PATH"]`, including Windows `PATHEXT`
extension probing.
> 
> Applies the same updated Node lookup logic across all four synced
helper-binstub copies (`lib/install/bin/*`, `spec/dummy/bin/*`, and the
`createBinStub` template in `package/configExporter/cli.ts`).
> 
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
1889de4. Bugbot is set up for automated
code reviews on this repo. Configure
[here](https://www.cursor.com/dashboard/bugbot).</sup>
<!-- /CURSOR_SUMMARY -->

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 47492995cc

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread lib/install/bin/shakapacker-config Outdated
Comment thread package/configExporter/cli.ts Outdated
Comment thread test/configExporter/createBinStub.test.js Outdated
@claude

claude Bot commented May 24, 2026

Copy link
Copy Markdown
Contributor

Code Review

Overview

This PR adds a Jest parity test that generates both helper binstubs via createBinStub and asserts they match the checked-in lib/install/bin/* files byte-for-byte. To support the test, createBinStub is exported from package/configExporter/cli.ts. The change is low-risk and well-targeted — it closes a real gap where JS/Ruby template drift would not have failed CI.


Strengths

  • Correct test strategy. Invoking the actual template function and comparing output to the checked-in file is exactly the right way to catch drift. A snapshot test would just encode the current state; this test encodes a contract.
  • Minimal change to production code. Only the export keyword and an explanatory comment are added — no behaviour change.
  • createBinStub not re-exported from index.ts. This keeps it out of the public ./configExporter API surface. Good hygiene.
  • test.each avoids duplicating the assertion body for the two helper names.
  • Proper temp-dir cleanup with recursive: true, force: true.

Issues / Suggestions

1. Export comment hard-codes the test file path (minor)

If the test file is ever moved or renamed, the comment in cli.ts silently lies. The intent ("this is exported for testing only") is worth preserving, but the concrete path is fragile. A JSDoc @internal tag or a shorter comment like "Exported for test use only" would be more resilient.

2. if (tmp) guard in afterEach is logically dead (nit)

beforeEach calls mkdtempSync, which is synchronous and throws on failure. If it throws, the test itself never runs, so afterEach is also never called. The if (tmp) check can never be false when afterEach executes.

3. ./package/* wildcard exposes compiled cli.js to npm consumers (informational)

The package.json exports map includes "./package/*": "./package/*", so createBinStub is now technically reachable by npm consumers. Unlikely to matter in practice but worth being aware of.


Not a concern here

  • The inline require("fs") for mkdirSync/chmodSync inside createBinStub (vs. the top-level import of other fs functions) is pre-existing and not introduced by this PR.
  • The test does not assert the executable bit (0o755); correct since content parity is what matters for drift detection.

Verdict

Approve with nits. The test closes a real CI gap with a minimal, well-structured change. The nits above are worth a quick fix but not blocking.

Comment thread package/configExporter/cli.ts Outdated
Comment thread test/configExporter/createBinStub.test.js
@claude

claude Bot commented May 24, 2026

Copy link
Copy Markdown
Contributor

Code Review

Overall: This is a well-scoped PR that closes a real gap — the existing Ruby sync spec couldn't reach into the JS template inside createBinStub, meaning JS↔Ruby drift was invisible to CI. The approach (export the function, add a Jest parity test) is the right tool for the job. A few minor observations:

package/configExporter/cli.ts

The added export keyword is minimal and correct. The "for test use only" comment sets the right expectation, but it's worth noting that the comment is advisory only — once exported, the function is callable from any downstream consumer of the module. If this is a concern, an alternative is using a separate internal utility module that isn't part of the public bundle. For this codebase's scale, the comment approach is a reasonable tradeoff.

One pre-existing style inconsistency is now more visible given the function is exported: mkdirSync and chmodSync are pulled in via require("fs") inside the function body, while the rest of the file uses static ESM imports at the top (existsSync, readFileSync, writeFileSync). This isn't a blocker for this PR, but now that createBinStub has public visibility it's a good moment to note it for follow-up.

test/configExporter/createBinStub.test.js

  • The test correctly exercises both shakapacker-config and diff-bundler-config, confirming that the basename-driven template logic produces the right .cjs script name in each case.
  • beforeEach/afterEach with mkdtempSync/rmSync is clean — no leftover artifacts across test runs.
  • The test verifies content only, not file permissions (0o755). That's acceptable; the Ruby integration tests cover executability, and umask behavior makes permission assertions fragile in CI anyway.
  • The long block comment at the top of the file explains context that's genuinely non-obvious (why the Ruby spec can't reach into the JS template), so it's warranted — though per project style it could be trimmed. Not a concern.

Nit: test only goes in one direction

The test asserts createBinStub output matches the installed files. It would silently pass if someone updates both the JS template and the Ruby install files identically (which is what you'd want), but it also passes if someone changes createBinStub's output to match an already-wrong installed file. That's the nature of byte-equality parity tests — the installed files are implicitly the source of truth. This is fine; just worth documenting in a future contributor guide or comment if the sync invariant isn't obvious.

Summary

No blocking issues. The PR achieves its stated goal — closing the JS↔Ruby binstub drift gap in CI — with minimal surface area. The require("fs") inconsistency inside createBinStub is pre-existing and out of scope here.

Comment thread package/configExporter/cli.ts Outdated
Comment thread test/configExporter/createBinStub.test.js
Comment thread test/configExporter/createBinStub.test.js
@claude

claude Bot commented May 24, 2026

Copy link
Copy Markdown
Contributor

Code Review

Overview: Closes a real coverage gap — the Ruby binstub_sync_spec.rb couldn't reach the JS string template inside createBinStub, so Ruby/JS drift would never fail CI. This PR exports the function and adds a byte-for-byte parity test. The scope is narrow and focused.


package/configExporter/cli.ts

Import refactor (lines 1–13): Good cleanup. Moving chmodSync and mkdirSync from a dynamic require("fs") inside createBinStub to the module-level static import block is the right call — it's consistent with the rest of the file's import style and avoids a CommonJS require() inside an otherwise-ESM-oriented module.

Export comment (line 519):

// Exported for test use only: verifies generated content matches lib/install/bin/* binstubs.

The project's coding style says comments should only capture the non-obvious why, and shouldn't reference the task or describe what callers do. The fact that the export is test-only is already communicated by the test file that imports it. Consider dropping this comment, or keeping just // exported for tests if you want the call-site signal preserved.


test/configExporter/createBinStub.test.js

Structure: Clean. test.each is the right tool for the two-variant parametric case; beforeEach/afterEach with mkdtempSync/rmSync is correct isolation.

Comment block (lines 7–15): The block explains why this test exists rather than what it does, which is a valid exception to the no-comments rule given how non-obvious the cross-language gap is. Reasonable trade-off.

Trailing newline: Present — ✓

Minor gap — permissions not asserted: Flagged inline. The test doesn't verify the generated file has executable bits set (0o755). Since createBinStub has a chmodSync call with a try/catch specifically to handle umask-stripped permissions, a test asserting stat.mode & 0o111 > 0 would lock in that behavior. Low severity since the PR's primary goal (content parity) is fully covered.


Overall

This is a solid, well-scoped PR. The core mechanism — invoking the real createBinStub and comparing output byte-for-byte against the checked-in templates — is exactly the right approach. No correctness bugs, no security concerns, no performance issues. The one actionable suggestion is adding a permission assertion to the new test; everything else is style-level.

@justin808 justin808 force-pushed the jg/1123-binstub-sync-coverage branch from dc4aec2 to d7202da Compare May 27, 2026 02:32
@justin808 justin808 changed the title Tighten binstub sync coverage and helper specs Tighten helper binstub parity coverage May 27, 2026
@justin808 justin808 force-pushed the jg/1123-binstub-sync-coverage branch from d7202da to 17b6e7f Compare May 27, 2026 09:42
@justin808 justin808 changed the title Tighten helper binstub parity coverage Add helper binstub parity coverage May 27, 2026
@justin808

Copy link
Copy Markdown
Member Author

@claude review this PR

@justin808 justin808 changed the title Add helper binstub parity coverage Tighten helper binstub parity and PATH lookup May 28, 2026

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 340ffa81e6

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread lib/install/bin/shakapacker-config
@claude

claude Bot commented May 28, 2026

Copy link
Copy Markdown
Contributor

Code Review

Overview

This is a well-scoped, low-risk PR that closes a meaningful gap: the embedded Ruby template inside createBinStub was previously the only copy of the helper-binstub logic that couldn't be checked by the Ruby sync specs. The new Jest parity test, improved sync-spec error message, and RSpec coverage for the empty-PATH edge case all strengthen the contract without adding complexity.

PATH Fix (path.empty? ? Dir.pwd : path)

Correct. POSIX specifies that an empty string in PATH means the current directory, but File.join("", "node") produces /node, not ./node. The fix is idiomatic and applied consistently across all four synchronized copies (both lib/install/bin/*, spec/dummy/bin/shakapacker-config, and the embedded template in cli.ts).

The timing is also right: shakapacker_find_executable is called before Dir.chdir(app_root), so Dir.pwd at lookup time is the launch directory — exactly where fake_node is placed in the test.

New RSpec test (helper_binstubs_spec.rb)

Solid structure. One minor clarity issue on the real_node_path lookup (see inline comment).

The "PATH" => "#{File::PATH_SEPARATOR}/nonexistent" trick is clever — prepending the separator creates a leading empty segment that exercises the new Dir.pwd branch. The skip guard for environments without node is appropriate.

Using RbConfig.ruby to launch the binstub directly with a stripped PATH is correct: Ruby is invoked by full path so it never needs to search for itself, and the binstub only require "rbconfig" (stdlib).

Jest test (createBinStub.test.js)

Clean. The test.each usage, beforeEach/afterEach cleanup, and accessSync(X_OK) executable check are all appropriate. The explanatory block comment accurately describes why this test exists and what the Ruby spec cannot cover.

binstub_sync_spec.rb failure message

The expanded message naming all four locations is an improvement for first-time contributors. The message is attached to the spec/dummy vs lib/install/shakapacker-config check rather than diff-bundler-config (which has its own test at line 66), so it's slightly broader than the specific failure — but that's the right call since all four copies move together in practice.

package/configExporter/cli.ts

Moving mkdirSync/chmodSync to the module-level import removes the inconsistent inline require("fs"). The export comment is honest about the purpose.

Gemfile.lock

Mechanical and necessary — the lockfile still referenced the RC version after the 10.1.0 release, breaking Bundler frozen-mode CI.

Summary

No blocking issues. One inline suggestion on variable naming in the new RSpec test (cosmetic). The approach, implementation, and test coverage are all sound.

Comment thread spec/shakapacker/helper_binstubs_spec.rb
justin808 and others added 8 commits May 27, 2026 22:42
Address the medium-priority test-coverage follow-ups from the PR #1104
review.

1. Cover spec/dummy/bin/shakapacker-config in the sync spec. The dummy
   binstub used by the test app must stay byte-identical to the install
   template; otherwise a stale wrapper could silently mask install-
   template changes.

2. Add a JS test that invokes createBinStub for both helper binstub
   names and asserts the generated content is byte-identical to the
   checked-in lib/install/bin/* files. Exports createBinStub from
   package/configExporter/cli.ts so the test can reach it. This closes
   the gap where the Ruby sync spec couldn't reach into the JS template.

3. Assert the `[Shakapacker] No Gemfile found at …` warning in
   helper_binstubs_spec.rb so the fallback path can't lose its warning
   silently.

4. Update the "Keep in sync" comment in all four locations to list all
   four copies (the previous wording said "all three" and missed the
   dummy binstub).

5. Rename the "documents every divergent binstub and only divergent
   binstubs" example to "all documented divergent binstubs still exist
   in both directories" so the name matches what the test actually
   verifies — the "only" guarantee comes from the iteration loop, not
   this example.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@justin808 justin808 force-pushed the jg/1123-binstub-sync-coverage branch from c37ff59 to 291baa6 Compare May 28, 2026 08:42
Comment thread package/configExporter/cli.ts Outdated
Comment thread spec/shakapacker/helper_binstubs_spec.rb Outdated
Comment thread spec/shakapacker/binstub_sync_spec.rb
@claude

claude Bot commented May 28, 2026

Copy link
Copy Markdown
Contributor

Code Review

Overview

This PR tightens helper binstub parity and fixes a real edge case: empty PATH segments (leading/trailing :) were silently dropped because Ruby's String#split omits trailing empty fields by default. The fix — using split(File::PATH_SEPARATOR, -1) and mapping empty segments to Dir.pwd — is the correct POSIX-aligned behavior. The byte-comparison JS parity test closing the template drift gap is a smart approach.


PATH bug fix ✅

The core fix in all four binstub copies is correct:

  • split(File::PATH_SEPARATOR, -1) preserves trailing empty fields that split would otherwise discard.
  • Mapping an empty segment to Dir.pwd matches standard shell PATH semantics (an empty entry means the current directory).
  • The new RSpec examples for leading/trailing empty entries are well-structured: chdir: launch_path puts real node in-scope via the empty segment, and the assertion verifies the binstub successfully chdir'd to app_root.

Parity test approach ✅

test/configExporter/createBinStub.test.js byte-comparing the generated output to the checked-in install files is the right strategy. Combined with the existing binstub_sync_spec.rb covering the three Ruby copies, there is now no gap in the parity contract.

Bundler.with_unbundled_env for the "node unavailable" test ✅

This was a latent correctness issue in the prior spec: Bundler's GEM_HOME/RUBYOPT could influence the subprocess. Wrapping it matches what the other subprocesses in the file already do.

Refactoring real_node_path to a helper ✅

Clean extraction. Note that the helper intentionally does not use split(-1), which is correct — it's searching the real test environment's PATH for an actual node binary and shouldn't treat CWD as a fallback there.


Minor items (see inline comments)

  1. createBinStub export API surface — exporting with a "test use only" comment works, but widens the public npm API permanently. Consider a @internal JSDoc annotation or exporting just the template string instead of the full function.
  2. ENV.fetch("PATH") in real_node_path — production binstubs use ENV.fetch("PATH", ""); matching that pattern in the helper would be more consistent.
  3. binstub_sync_spec error message — the "all four copies" instruction is correct but benefits from a small clarifying prefix explaining why all four need updating.

Summary

Solid, well-tested PR. The PATH fix is correct, the parity test closes a real coverage gap, and the Bundler env fix eliminates a latent test reliability issue. The three inline items are all nits — nothing blocks merging.

@claude

claude Bot commented May 28, 2026

Copy link
Copy Markdown
Contributor

Code Review: Tighten helper binstub parity and PATH lookup

Overall: looks solid — correct fix, good test coverage, clean refactoring. One minor nit below.


PATH fix (lib/install/bin/*, spec/dummy/bin/shakapacker-config, createBinStub template)

The split(File::PATH_SEPARATOR, -1) change is correct: the negative limit preserves trailing empty fields that Ruby's default split silently drops. Mapping an empty segment to Dir.pwd matches standard POSIX PATH semantics (: = current directory). The fix is applied uniformly across all four copies, which is exactly right.

Test coverage

  • 4 new RSpec examples (2 positions × 2 commands) cleanly exercise the edge case. The indirection through a fake_node_path wrapper is a neat approach: placing node only in launch_path means a passing test proves the empty segment was resolved as Dir.pwd — there's nowhere else node could have been found.
  • Wrapping the existing "node unavailable" test in Bundler.with_unbundled_env is the right fix; without it the Bundler-activated environment can bleed through and mask a missing node.
  • Extracting the inline node-lookup into real_node_path reduces duplication across three callsites. Clean.

createBinStub export + parity test

Exporting createBinStub for test use only, while keeping it out of index.ts, is a reasonable encapsulation choice. The JS test closes the gap the Ruby sync spec couldn't reach (the TypeScript template string), and the byte-for-byte comparison is a strong contract.

binstub_sync_spec.rb error message

The updated failure message naming all four copies is a genuine improvement for contributors — much easier to act on than the previous two-copy guidance.

Import refactoring in cli.ts

Moving mkdirSync/chmodSync from inline require("fs") to the module-level ESM import is correct idiomatic TypeScript.


Minor nit

real_node_path uses ENV.fetch("PATH") with no default. If PATH were unset it would raise KeyError before the skip guard on the callsites could handle it. This matches the inline code it replaced, so it's not a regression, but ENV.fetch("PATH", "") would make the failure mode graceful. Very low priority.

Comment thread spec/shakapacker/helper_binstubs_spec.rb Outdated
justin808 and others added 2 commits May 28, 2026 22:38
Address remaining optional review nits on PR #1128:

- spec/shakapacker/helper_binstubs_spec.rb: use ENV.fetch("PATH", "")
  in the real_node_path helper so it degrades gracefully (matching the
  production binstubs) instead of raising KeyError if PATH is unset.
- spec/shakapacker/binstub_sync_spec.rb: clarify the divergence failure
  message to explain that all four copies must stay byte-for-byte
  identical, so a contributor who edits only one copy understands the
  full set to update.

Test-only changes; no user-visible behavior change.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Address review trade-off on PR #1128: exporting createBinStub for the
parity test widens the importable surface. It is reachable only via the
deep package/configExporter/cli path (not the curated ./configExporter
entry), but a JSDoc @internal tag documents that it is test-only and not
part of the public API, discouraging downstream production use.

Comment-only change; the generated template string is untouched, so the
byte-for-byte binstub parity test is unaffected.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@justin808

Copy link
Copy Markdown
Member Author

@claude please review this PR. Focus on:

  • Code quality and best practices
  • Potential bugs or issues
  • Security implications
  • Performance considerations

Please post your review as a PR comment, and use inline comments for specific code issues.

@justin808 justin808 merged commit 91a6d89 into main May 29, 2026
60 of 61 checks passed
@justin808 justin808 deleted the jg/1123-binstub-sync-coverage branch May 29, 2026 22:41
justin808 added a commit that referenced this pull request Jun 9, 2026
…nalyze-webpack-output

* origin/main: (164 commits)
  Make Rspack the default bundler for new installs (#1150)
  Add "Why Shakapacker with Rspack" comparison guide (#1155)
  Support sass-loader v17 (#1141)
  [codex] Fix Claude workflow permissions (#1153)
  Fix README CI badges and add prominent docs link (#1152)
  Trim README into a docs-site index; relocate content to docs/ and fix deep links (#1148)
  Tighten helper binstub parity and PATH lookup (#1128)
  Optimize CI: minimal version matrix on PRs, full matrix on main (#1151)
  Fix instance-scoped compiler strategies (#1147)
  Fix Rspack devServer in static watch mode (#1142)
  Add homepage links for supplemental npm packages (#1145)
  Release 10.1.0
  Update CHANGELOG.md for v10.1.0 (#1139)
  Expand prerelease-to-stable changelog coalesce guidance in /update-changelog (#1138)
  Release 10.1.0-rc.2
  [codex] Update changelog for v10.1.0-rc.2 (#1136)
  Teach /update-changelog to drop RC-only regression fixes (#1125)
  Restructure supplemental package dependencies (#1131) (#1133)
  [codex] Add Shakapacker brand assets (#1135)
  [codex] Fix shakapacker config helper binstubs (#1132)
  ...
justin808 added a commit that referenced this pull request Jun 29, 2026
* origin/main: (26 commits)
  Adopt agent-workflow binstubs (.agents/bin/ + AGENTS.md pointer) (#1176)
  Fix Ruby 3.1 CI: exclude broken i18n 1.15.0/1.15.1 (Fiber[]) (#1171)
  Add AI analysis prompt generator to config exporter (#695)
  Make Rspack the default bundler for new installs (#1150)
  Add "Why Shakapacker with Rspack" comparison guide (#1155)
  Support sass-loader v17 (#1141)
  [codex] Fix Claude workflow permissions (#1153)
  Fix README CI badges and add prominent docs link (#1152)
  Trim README into a docs-site index; relocate content to docs/ and fix deep links (#1148)
  Tighten helper binstub parity and PATH lookup (#1128)
  Optimize CI: minimal version matrix on PRs, full matrix on main (#1151)
  Fix instance-scoped compiler strategies (#1147)
  Fix Rspack devServer in static watch mode (#1142)
  Add homepage links for supplemental npm packages (#1145)
  Release 10.1.0
  Update CHANGELOG.md for v10.1.0 (#1139)
  Expand prerelease-to-stable changelog coalesce guidance in /update-changelog (#1138)
  Release 10.1.0-rc.2
  [codex] Update changelog for v10.1.0-rc.2 (#1136)
  Teach /update-changelog to drop RC-only regression fixes (#1125)
  ...

# Conflicts:
#	CHANGELOG.md
#	lib/install/bin/diff-bundler-config
#	lib/install/bin/shakapacker-config
#	lib/tasks/shakapacker/export_bundler_config.rake
#	package/configExporter/cli.ts
#	spec/dummy/bin/shakapacker-config
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.

1 participant