Skip to content

resolver: keep forward slashes when imports target is a package specifier#30845

Merged
Jarred-Sumner merged 3 commits into
mainfrom
farm/3e839559/windows-imports-scoped-posix-join
May 16, 2026
Merged

resolver: keep forward slashes when imports target is a package specifier#30845
Jarred-Sumner merged 3 commits into
mainfrom
farm/3e839559/windows-imports-scoped-posix-join

Conversation

@robobun

@robobun robobun commented May 15, 2026

Copy link
Copy Markdown
Collaborator

Repro

Per the reporter on Windows (also reproduced on main):

// packages/app/package.json
{
  "imports": { "#res": "@myproject/resolver" }
}

// packages/resolver/package.json
{
  "name": "@myproject/resolver",
  "main": "./index.cjs",
  "exports": { ".": "./index.mjs" }
}
import { type } from '#res';   // Node: 'esm (from exports)'
                                // Bun (Windows): 'cjs (from main)'

Linux/macOS output the expected ESM value; Windows falls back to the legacy main field.

Cause

In src/resolver/package_json.rs resolve_target, the branch that handles an imports target which is itself a bare package specifier (not ./…, ../…, /…) joins [target, subpath] via resolve_path::platform::Auto and hands the joined string back to package-resolve for a second pass.

platform::Auto is platform::Windows on Windows, so the join calls normalize_string_node_t::<u8, Windows> which rewrites / to \. The scoped package name @myproject/resolver becomes @myproject\resolver; that no longer matches anything in node_modules and the resolver falls back to legacy main-field lookup, producing the CJS file.

Per the Node.js packages spec, values inside imports and exports are URL-like specifiers that always use forward slashes — they are not OS-specific filesystem paths and must not be normalized to backslashes. The Zig reference (package_json.zig:1782) uses .auto here too; the Rust port inherited the bug.

Fix

Use platform::Posix for this one join so the scoped-package / is preserved. The other two platform::Auto joins in the same function operate on [package_url, str] / [package_url, str, subpath], where the result IS a real filesystem path — Windows normalization there is correct and stays as-is.

Test

Added describe.if(isWindows)("#30839 - imports entry pointing at a scoped package", …) in test/js/bun/resolve/resolve.test.ts that mirrors the reporter's setup: a workspace with @myproject/resolver dual-built (main=cjs, exports=mjs) and an imports entry mapping #res to the scoped name. Asserts that running bun test.mjs outputs the ESM value.

The test is Windows-only because on Linux/macOS platform::Auto is already platform::Posix and this join is a no-op for forward-slash input; the misbehavior only surfaces when the join's platform is Windows. That matches the existing test.if(isWindows) regression pattern in the repo (e.g. test/regression/issue/23292.test.ts).

Verification

  • Linux: bun bd test test/js/bun/resolve/resolve.test.ts — 37 pass, 2 skip (including the new Windows-only case), 0 fail.
  • Reproduction script from the issue, run against build/debug/bun-debug on Linux, prints Resolved type: esm (from exports) both before and after the fix (expected — Linux isn't affected).

Fixes #30839

…cifier

Per the Node.js packages spec, values in imports/exports are URL-like
specifiers and must always use forward slashes. When an imports entry
maps a private name to a bare package specifier (e.g. "#res":
"@myproject/resolver"), the resolver joined the target with the
remaining subpath via platform::Auto and fed the result back into
package-resolve.

On Windows, platform::Auto is Windows, so the join normalized the / in
the scoped-package name to \, producing @myproject\resolver. That no
longer matches a scoped package on subsequent lookup and Bun falls back
to the legacy main field instead of using exports.

Use platform::Posix for this single join so the scoped-package / is
preserved. The other platform::Auto joins in the same function operate
on [package_url, str] / [package_url, str, subpath] where the result is
a filesystem path and Windows normalization is correct.

The Zig port (package_json.zig:1782) had the same .auto bug — matched
here just in case the Zig is revived.

Fixes #30839
@coderabbitai

coderabbitai Bot commented May 15, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 72b48e72-1018-4790-81a8-fd5bfa05e9dd

📥 Commits

Reviewing files that changed from the base of the PR and between 314d044 and 8de894f.

📒 Files selected for processing (12)
  • src/crash_handler/lib.rs
  • src/errno/lib.rs
  • src/perf/tracy.rs
  • src/resolver/package_json.rs
  • src/runtime/cli/Arguments.rs
  • src/runtime/cli/run_command.rs
  • src/runtime/cli/upgrade_command.rs
  • src/runtime/jsc_hooks.rs
  • src/runtime/webview/ChromeProcess.rs
  • src/spawn/process.rs
  • src/spawn_sys/spawn_process.rs
  • test/js/bun/resolve/resolve.test.ts

Walkthrough

This PR fixes scoped package imports resolution on Windows by switching path joining from platform-aware to Posix semantics, preserving forward slashes required for URL-like package specifiers. Supporting formatting refactors standardize multi-line platform configuration attributes across crash handling, runtime startup, process spawning, and system monitoring modules.

Changes

Windows scoped package imports fix and platform configuration cleanup

Layer / File(s) Summary
Scoped package imports resolution for #30839
src/resolver/package_json.rs, test/js/bun/resolve/resolve.test.ts
Imports resolution for package-specifier-like targets now uses Posix path joining to preserve forward slashes on Windows, preventing fallback to main fields. Regression test validates exports resolution via scoped package imports on Windows with junction symlinks.
Crash handler and error diagnostics platform configuration
src/crash_handler/lib.rs, src/errno/lib.rs
Platform-specific conditionals for crash handler initialization, error reporting, and error number mapping tests are reformatted from single-line to multi-line #[cfg(...)] form while maintaining identical supported target OS sets.
Performance monitoring platform detection
src/perf/tracy.rs
Tracy profiler's dlsym path initialization platform condition is reformatted into multi-line form, preserving conditional logic for target OS selection.
Runtime startup and optimization link sections
src/runtime/cli/Arguments.rs, src/runtime/cli/run_command.rs, src/runtime/cli/upgrade_command.rs
Startup table and cold-path function link section attributes are reformatted from single-line to multi-line #[cfg_attr(...)] form, maintaining Linux/Android-specific .rodata.startup and .text.unlikely placement.
File operations and temp path handling
src/runtime/jsc_hooks.rs
Embedded file temp path construction is reformatted into clearer multi-line expressions without semantic change to filename or directory resolution.
Webview and developer tools platform configuration
src/runtime/webview/ChromeProcess.rs
Playwright cache fallback and dev tools port detection conditionals are reformatted into multi-line form, preserving platform-specific paths for Linux/Android and cross-platform support.
Process spawning and lifecycle platform handling
src/spawn/process.rs, src/spawn_sys/spawn_process.rs
POSIX process spawning logic and orphan handling predicates are reformatted into multi-line conditionals, maintaining platform predicates for Linux, Android, and macOS with adjusted label warning suppression scope.

Possibly related PRs

  • oven-sh/bun#30720: Introduces or refactors the resolve_embedded_file_to_buf helper in src/runtime/jsc_hooks.rs that this PR reformats.
  • oven-sh/bun#30735: Functionally adjusts the same Android/Linux-compat platform gates in crash handler, perf/tracy, runtime/cli, and runtime/webview modules that this PR reformats for consistency.
🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and specifically describes the main fix: using Posix path joining to preserve forward slashes in imports targets that are package specifiers.
Description check ✅ Passed The description comprehensively covers the repro, cause analysis, fix explanation, test verification, and links to issue #30839, following the template sections.
Linked Issues check ✅ Passed The PR directly addresses the core requirement from #30839: using platform::Posix instead of platform::Auto when joining imports/exports specifiers to preserve forward slashes on Windows.
Out of Scope Changes check ✅ Passed All changes are scoped to fixing the Windows resolver bug: the primary fix in package_json.rs, supporting cfg reformatting across multiple files for code consistency, and a Windows-only regression test.

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


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

@robobun

robobun commented May 15, 2026

Copy link
Copy Markdown
Collaborator Author
Updated 2:57 PM PT - May 15th, 2026

@robobun, your commit cdd4869 has 1 failures in Build #54936 (All Failures):


🧪   To try this PR locally:

bunx bun-pr 30845

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

bun-30845 --bun

@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.

LGTM — one-word resolver fix (AutoPosix) with a targeted Windows regression test; the other 10 files are pure cargo fmt output from autofix.ci with no semantic change.

Extended reasoning...

Overview

The substantive change is a single identifier in src/resolver/package_json.rs (resolve_target): the path join for an imports target that is itself a bare package specifier now uses platform::Posix instead of platform::Auto. A Windows-only regression test is added to test/js/bun/resolve/resolve.test.ts. The remaining 10 files (crash_handler, errno, perf/tracy, runtime/cli/*, jsc_hooks, webview/ChromeProcess, spawn/*) are exclusively whitespace — #[cfg(...)] / #[cfg_attr(...)] attributes reflowed from single-line to multi-line form by the autofix.ci cargo fmt --all commit (8de894f), which follows the recently-merged #30682 that switched the format workflow to cargo fmt. I checked each hunk; none alter the predicate set or any token other than whitespace.

Security risks

None. The change narrows path normalization for a URL-like package specifier (per the Node.js packages spec, imports/exports values are always forward-slash). It does not touch filesystem-path joins (the two adjacent platform::Auto joins on package_url are correctly left as-is), auth, crypto, permissions, or any user-input parsing surface.

Level of scrutiny

Low. The logic delta is one word with a clear, well-argued PORT NOTE comment and PR description tying it to the Node spec and the original Zig line. On non-Windows targets platform::Auto already equals platform::Posix, so the change is a no-op everywhere except the broken Windows path it fixes. The new test mirrors the reporter's setup exactly and uses the established describe.if(isWindows) pattern. The autofix-applied formatting churn is mechanical and verifiably semantics-preserving.

Other factors

The robobun build failures cited are for commit b1d7b4d (pre-autofix) on the build-rust step across several targets; given the autofix commit that followed is purely cargo fmt output, those were format-check failures now resolved by 8de894f. No human reviewer comments are outstanding, no CODEOWNERS gating, and the bug-hunting system found nothing.

@craftingmod

craftingmod commented May 15, 2026

Copy link
Copy Markdown
x bun-30845 test
bun test v1.3.14-canary.1 (8de894fec)

packages\main\bun\index.test.ts:
✓ Resolve 'resolver' module directly [1.45ms]
✓ Resolve 'resolver' module from subpath import [0.09ms]
✓ Resolve 'exporter' module directly [0.71ms]
✓ Resolve 'exporter' module from subpath import [0.08ms]

It works 👀

Test repo: https://github.com/craftingmod/bun-subpath-import-slash

@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.

LGTM — one-line Posix-join fix for #30839 plus a Windows regression test; the other 10 files are pure cargo fmt reflows from autofix.ci.

Extended reasoning...

Overview

The substantive change is a single token swap in src/resolver/package_json.rs (platform::Autoplatform::Posix) inside the resolve_target branch that handles an imports target which is itself a bare package specifier (gated by internal && !./ && !../ && !/, returning Status::PackageResolve). A Windows-only regression test is added to test/js/bun/resolve/resolve.test.ts. The remaining 10 files (crash_handler, errno, tracy, Arguments.rs, run_command.rs, upgrade_command.rs, jsc_hooks.rs, ChromeProcess.rs, spawn/process.rs, spawn_sys/spawn_process.rs) are byte-for-byte semantic no-ops: #[cfg(...)]/#[cfg_attr(...)] attributes reflowed from single-line to multi-line by the [autofix.ci] apply automated fixes commit, following the recent switch of the format workflow to cargo fmt --all.

Security risks

None. No input parsing, auth, crypto, or privilege boundaries are touched. The resolver change only affects which path-separator style is used when concatenating a bare specifier with a subpath before re-entering package resolution; it cannot introduce traversal because the result is fed back into the package resolver (not opened as a filesystem path), and the ../ / / prefixes are already excluded by the surrounding guards.

Level of scrutiny

Low. The fix is one line whose correctness follows directly from the Node.js packages spec (imports/exports targets are URL-like, always /). On Linux/macOS Auto == Posix, so behavior is identical there; on Windows it now preserves the scoped-package / instead of normalizing to \. The PR description's analysis matches the code, the sibling PATTERN arm already builds a forward-slash specifier via string replace (no platform join), and the issue reporter independently confirmed the fix works on Windows. The formatting hunks require no review beyond confirming they are whitespace-only, which they are.

Other factors

A targeted Windows regression test is included and follows existing repo patterns (describe.if(isWindows), tempDir, junction symlink for the workspace link). CodeRabbit found nothing actionable, the bug-hunter system found no bugs, and there are no outstanding human review comments.

@robobun

robobun commented May 15, 2026

Copy link
Copy Markdown
Collaborator Author

CI diff is green: all Windows lanes pass (the target of this fix), all macOS lanes pass, all Rust/C++ build lanes pass. The overall buildkite/bun is marked failing because of one flaky shard — shard 15 of 20 on :ubuntu: 25.04 aarch64 - test-bun exited with code 2 (test failure).

This change is in src/resolver/package_json.rs and swaps platform::Auto for platform::Posix in a single path-join. On Linux/macOS platform::Auto is already platform::Posix, so the change is a compile-time no-op on every non-Windows target — it cannot cause a Linux test failure. (The earlier attempt had darwin-14-aarch64 Expired — runner expiration, also unrelated; I used my single retrigger on that one.)

Stopping here per the CI-flake rule (one re-roll max). Needs a maintainer to re-run the flaky lane or merge.

@Jarred-Sumner Jarred-Sumner merged commit 2a3d0e7 into main May 16, 2026
76 of 77 checks passed
@Jarred-Sumner Jarred-Sumner deleted the farm/3e839559/windows-imports-scoped-posix-join branch May 16, 2026 03:47
robjtede pushed a commit to robjtede/bun that referenced this pull request May 16, 2026
…fier (oven-sh#30845)

## Repro

Per the reporter on Windows (also reproduced on main):

```jsonc
// packages/app/package.json
{
  "imports": { "#res": "@myproject/resolver" }
}

// packages/resolver/package.json
{
  "name": "@myproject/resolver",
  "main": "./index.cjs",
  "exports": { ".": "./index.mjs" }
}
```

```js
import { type } from '#res';   // Node: 'esm (from exports)'
                                // Bun (Windows): 'cjs (from main)'
```

Linux/macOS output the expected ESM value; Windows falls back to the
legacy `main` field.

## Cause

In `src/resolver/package_json.rs` `resolve_target`, the branch that
handles an `imports` target which is itself a bare package specifier
(not `./…`, `../…`, `/…`) joins `[target, subpath]` via
`resolve_path::platform::Auto` and hands the joined string back to
package-resolve for a second pass.

`platform::Auto` is `platform::Windows` on Windows, so the join calls
`normalize_string_node_t::<u8, Windows>` which rewrites `/` to `\`. The
scoped package name `@myproject/resolver` becomes `@myproject\resolver`;
that no longer matches anything in `node_modules` and the resolver falls
back to legacy main-field lookup, producing the CJS file.

Per the Node.js packages spec, values inside `imports` and `exports` are
URL-like specifiers that always use forward slashes — they are not
OS-specific filesystem paths and must not be normalized to backslashes.
The Zig reference (`package_json.zig:1782`) uses `.auto` here too; the
Rust port inherited the bug.

## Fix

Use `platform::Posix` for this one join so the scoped-package `/` is
preserved. The other two `platform::Auto` joins in the same function
operate on `[package_url, str]` / `[package_url, str, subpath]`, where
the result IS a real filesystem path — Windows normalization there is
correct and stays as-is.

## Test

Added `describe.if(isWindows)("oven-sh#30839 - imports entry pointing at a
scoped package", …)` in `test/js/bun/resolve/resolve.test.ts` that
mirrors the reporter's setup: a workspace with `@myproject/resolver`
dual-built (main=cjs, exports=mjs) and an `imports` entry mapping `#res`
to the scoped name. Asserts that running `bun test.mjs` outputs the ESM
value.

The test is Windows-only because on Linux/macOS `platform::Auto` is
already `platform::Posix` and this join is a no-op for forward-slash
input; the misbehavior only surfaces when the join's platform is
`Windows`. That matches the existing `test.if(isWindows)` regression
pattern in the repo (e.g. `test/regression/issue/23292.test.ts`).

## Verification

- Linux: `bun bd test test/js/bun/resolve/resolve.test.ts` — 37 pass, 2
skip (including the new Windows-only case), 0 fail.
- Reproduction script from the issue, run against
`build/debug/bun-debug` on Linux, prints `Resolved type: esm (from
exports)` both before and after the fix (expected — Linux isn't
affected).

Fixes oven-sh#30839

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
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.

Resolver: imports resolution for scoped packages ignores exports on Windows due to path separator corruption

3 participants