Skip to content

bundler: match process.env.X case-insensitively for --env inline on Windows - #34560

Closed
robobun wants to merge 3 commits into
mainfrom
farm/43b66dfa/env-inline-windows-case
Closed

robobun wants to merge 3 commits into
mainfrom
farm/43b66dfa/env-inline-windows-case

Conversation

@robobun

@robobun robobun commented Jul 18, 2026 •

Copy link
Copy Markdown
Collaborator

Problem

On Windows, bun build --env inline (and Bun.build({ env: "inline" })) does not substitute process.env.PATH unless the source spells it exactly process.env.Path, because Windows reports the variable as Path and the bundler's define lookup is case-sensitive.

> bun build --env inline -e "console.log(process.env.PATH)"
console.log(process.env.PATH);            // not inlined

> bun build --env inline -e "console.log(process.env.Path)"
console.log("C:\\Program Files\\...");    // inlined

At runtime process.env.PATH, process.env.Path and process.env.path all return the same value on Windows, so the inline should too. The same applies to --env PREFIX_*: the prefix was matched case-sensitively against the stored key, so --env SYS* never matched SystemRoot.

This is what makes test/bundler/bundler_env.test.ts > env/inline system fail on Windows CI (it prints the runtime PATH instead of the bundle-time value).

Cause

copy_env_for_define iterates the env loader's stored keys (which keep the OS case on Windows) and builds define keys like process.env.Path. Those land in Define.dots, which is a case-sensitive StringHashMap keyed by the last segment, so process.env.PATH in source never matches.

Fix

Define gains a dedicated env_dots map for env-derived process.env.X entries, keyed by the env-var name alone. On Windows it is a CaseInsensitiveAsciiStringArrayHashMap; on other platforms it is the regular case-sensitive StringArrayHashMap. The e_dot visitor consults env_dots after the existing dots lookup, guarded on optional_chain.is_none() so process.env?.X is left alone (matching the dots path), and verifies the target is an unbound process.env via is_dot_define_match before hashing the name into env_dots.

is_dot_define_match is made generic over AsRef<[u8]> so the ["process","env"] prefix can be passed as a const slice.

copy_env_for_define now matches the --env PREFIX_* prefix case-insensitively on Windows.

A side effect of routing env-derived entries through a separate map checked after dots: an explicit --define process.env.X=... now wins over an env-derived value for the same key. That matches the existing process.env.NODE_ENV insert-only-if-absent handling and is the behaviour #33857 is after.

Verification

New tests in test/bundler/bundler_env.test.ts:

  • bundler/env via spawn > process.env.X matches the env-var name case-insensitively on Windows only: spawns bun build --env inline directly (bypasses itBundled, so it registers on Windows today). On Windows all three casings inline; on POSIX only the exact case. Fails on released Windows, passes with the fix.
  • bundler/env via spawn > --env PREFIX_* matches case-insensitively on Windows only: same pattern for prefix mode. Fails on released Windows, passes with the fix.
  • bundler/env via spawn > process.env?.X is not inlined by --env inline: regression guard that the optional-chain form stays untouched.
  • env/inline-explicit-define-wins: explicit --define beats env-derived. Fails on the released binary on all platforms, passes with the fix.
  • env/inline-env-var-name-case and env/prefix-env-var-name-case: itBundled equivalents of the first two (need test/bundler: stop silently dropping every itBundled test on Windows #34552 to register on the Windows lanes).

All 13 bundler_env.test.ts tests pass with the fix on linux-x64 (1 fails on the released binary) and windows-x64 (2 of the 3 direct-spawn tests fail on the released binary). bundler_edgecase, bundler_string, bundler_browser, bundler_bun, bundler_cjs2esm and cli/run/env.test.ts pass unchanged. cargo check -p bun_js_parser -p bun_bundler --target x86_64-pc-windows-msvc is clean.


no test proof · iteration 0 · Platform-specific test(s) that do not run on this machine. Deferring to CI, which covers all platforms: test/bundler/bundler_env.test.ts

@coderabbitai

coderabbitai Bot commented Jul 18, 2026 •

Copy link
Copy Markdown
Contributor

Review Change Stack

Walkthrough

Changes

The change adds platform-specific storage for environment-derived defines, routes process.env.* values through that storage, applies them during parsing, updates option initialization and cloning, generalizes dot-define matching, and adds Windows/POSIX bundler tests.

Environment define inlining

Layer / File(s) Summary
Environment define storage and initialization
src/js_parser/lib.rs, src/bundler/defines.rs, src/bundler/options.rs
Defines now include platform-specific env_dots storage; dotenv prefix checks and string-define routing use it, while option construction and worker cloning initialize and copy the map.
Parser matching and process.env substitution
src/js_parser/p.rs, src/js_parser/visit/visit_expr.rs
Dot-define matching accepts generic byte-like parts, and property access resolves process.env.* entries from env_dots while preserving explicit define handling and removal flags.
Bundler behavior validation
test/bundler/bundler_env.test.ts
Tests cover explicit define precedence, platform-specific environment casing, and dotenv prefix matching.

Suggested reviewers: jarred-sumner

🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 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.
Title check ✅ Passed The title accurately states the main change: case-insensitive process.env matching for inline env on Windows.
Description check ✅ Passed It explains the problem, fix, and verification, covering the required substance even though the headings differ from the template.

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

@robobun

robobun commented Jul 18, 2026 •

Copy link
Copy Markdown
Collaborator Author
Updated 12:12 AM PT - Jul 18th, 2026

❌ @robobun, your commit 5e61e64 has 1 failures in Build #75109 (All Failures):


🧪   To try this PR locally:

bunx bun-pr 34560

That installs a local version of the PR into your bun-34560 executable, so you can run:

bun-34560 --bun

@github-actions

Copy link
Copy Markdown
Contributor

This PR may be a duplicate of:

  1. bundler: let explicit define win over env-derived process.env.X #33857 - Both fix explicit --define winning over env-derived process.env.X in src/bundler/defines.rs; bundler: match process.env.X case-insensitively for --env inline on Windows #34560 subsumes bundler: let explicit define win over env-derived process.env.X #33857's fix as part of its broader case-insensitive env matching change

🤖 Generated with Claude Code

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

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 `@src/js_parser/visit/visit_expr.rs`:
- Around line 1403-1433: Extract the shared define substitution and DCE-flag
propagation from the preceding dots handling and the env_dots block into a
helper, then use it for both paths. Ensure the helper also propagates
method_call_must_be_replaced_with_undefined so both define sources remain
behaviorally aligned, while preserving each block’s existing lookup and
precedence behavior.
🪄 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: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 66163385-39b6-4464-88a1-d3aad82b2b40

📥 Commits

Reviewing files that changed from the base of the PR and between 0170259 and 2a495fc.

📒 Files selected for processing (6)
  • src/bundler/defines.rs
  • src/bundler/options.rs
  • src/js_parser/lib.rs
  • src/js_parser/p.rs
  • src/js_parser/visit/visit_expr.rs
  • test/bundler/bundler_env.test.ts

Comment thread src/js_parser/visit/visit_expr.rs
Comment thread src/js_parser/visit/visit_expr.rs Outdated

@claude claude Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Thanks for addressing the optional-chain feedback — the e_.optional_chain.is_none() guard and the process.env?.X regression test cover it. Beyond the inline nit, I also checked that all three Define { .. } literal sites include env_dots, that e_index never consulted dots (so process.env["X"] behavior is unchanged), and that the referenced CaseInsensitiveAsciiStringArrayHashMap / starts_with_case_insensitive_ascii helpers exist. Leaving for a human to sign off given the user-visible precedence change (explicit --define now beats env-derived) and the overlap with #33857.

Extended reasoning...

The author's follow-up commit (5e61e64) addressed my prior review: the env_dots block is now gated on e_.optional_chain.is_none(), the lookup order was inverted to check is_dot_define_match before the map lookup, and a direct-spawn regression test asserts process.env?.X is not inlined. I verified the guard is present at visit_expr.rs:1407 and the test at bundler_env.test.ts:268-273. I also confirmed all three struct-literal construction sites of Define (defines.rs:259, options.rs:1459/1748) carry the new env_dots field, that e_index has no defines.dots lookup so bracket-access inlining behavior is unchanged by moving env entries out of dots, and that the Windows-only helpers referenced (bun_collections::CaseInsensitiveAsciiStringArrayHashMap, bun_core::strings::starts_with_case_insensitive_ascii) exist in-tree. Deferring rather than approving because this changes user-visible --define vs --env inline precedence on all platforms, adds a per-dot-expression check in the parser hot path, and subsumes open PR #33857 — a maintainer should confirm the precedence change and decide the fate of #33857.

Comment thread test/bundler/bundler_env.test.ts
@robobun

robobun commented Jul 18, 2026

Copy link
Copy Markdown
Collaborator Author

CI: the only hard failure in build 75109 is test/js/node/test/parallel/test-worker-message-port-transfer-terminate.js SIGABRT on debian 13 x64-asan, which is pre-existing on main (JSC exception-scope assertion during worker terminate) and unrelated to this diff. Everything else is flaky and passed on retry. test/bundler/bundler_env.test.ts passed on every lane including Windows. The diff is green; ready for a maintainer to merge past the unrelated main break.

@robobun

robobun commented Sep 13, 2026

Copy link
Copy Markdown
Collaborator Author

Closing as part of a cleanup of stale pull requests. This PR has had no new commits since 2026-07-18, it conflicts with main, and its last CI run failed. This is not a judgment on the fix itself. If the problem still reproduces on a current build, reopen this PR after a rebase or open a new one against main.

@robobun robobun closed this Sep 13, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant