Skip to content
Merged
Show file tree
Hide file tree
Changes from 24 commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
b3716ef
test/bundler: make the caller-location check work on Windows
robobun Jul 18, 2026
c43e2fc
address review + CI: %7E in pathToFileURL expectation, gate env/inlin…
robobun Jul 18, 2026
61a2c55
ci: retrigger
robobun Jul 18, 2026
16305aa
Merge remote-tracking branch 'origin/main' into farm/5967cb27/fix-bun…
dylan-conway Aug 28, 2026
49b9a62
package.json sideEffects: build the patterns with the native path syntax
dylan-conway Aug 28, 2026
1dce319
Windows: HTML import manifest 'input' relative to the root with the n…
dylan-conway Aug 28, 2026
1189a74
[autofix.ci] apply automated fixes
autofix-ci[bot] Aug 28, 2026
ac32ea7
remove itBundled-registers.test.ts
dylan-conway Aug 28, 2026
f96c33f
Windows: package name of a node_modules file path splits on the platf…
dylan-conway Aug 28, 2026
64682e8
test: LF-normalize the text-loader fixture (a Windows checkout may gi…
dylan-conway Aug 28, 2026
f4e5ad8
chunk paths: drop the Windows-only normalize branch so '[dir]/[name].…
dylan-conway Aug 28, 2026
a863ce3
test timeouts: room for writing 7000 fixture files / four compile sub…
dylan-conway Aug 28, 2026
a6c552c
Windows: follow the .exe outfile in the compile assertions; manifest …
robobun Aug 28, 2026
99c59a4
asset [dir] / no-bundle entry naming: relative to the root with the n…
dylan-conway Aug 28, 2026
cf79fbe
env test: fallback if no plain uppercase variable exists
dylan-conway Aug 28, 2026
d085d31
expectBundled: consume timeoutScale (it fell into unknownProps and si…
dylan-conway Aug 28, 2026
38f3a42
review: pass the un-suffixed outfile to bun (module name stays /root/…
dylan-conway Aug 28, 2026
6c88ffe
one-line doc comments
dylan-conway Aug 28, 2026
b0fdd11
itBundled: an option the backend does not implement registers the tes…
dylan-conway Aug 28, 2026
0d8a818
mode type: convertformat too
dylan-conway Aug 28, 2026
3333449
review: manifest helper writes into a reused buffer; module_dest_path…
dylan-conway Aug 28, 2026
480dc6f
one-line doc comment
dylan-conway Aug 28, 2026
0953655
[autofix.ci] apply automated fixes
autofix-ci[bot] Aug 28, 2026
7d42287
mode is a harness limitation on either backend
dylan-conway Aug 28, 2026
c88c4a2
debug launch config points at the .exe too
dylan-conway Aug 28, 2026
d6da3df
Merge branch 'main' into farm/5967cb27/fix-bundler-test-windows-path-…
dylan-conway Aug 29, 2026
835d8e1
harness: unforwarded esbuild options are a harness limitation on eith…
robobun Aug 29, 2026
1c8eaa3
review: collect_compile_assets no longer re-slashes (dest paths are '…
dylan-conway Aug 29, 2026
6f7756b
[autofix.ci] apply automated fixes
autofix-ci[bot] Aug 29, 2026
9f87d1f
npm/ReactSSR: keep the original mapping expectations (they hold on cu…
dylan-conway Aug 29, 2026
5f764b3
Merge branch 'farm/5967cb27/fix-bundler-test-windows-path-check' of g…
dylan-conway Aug 29, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 22 additions & 6 deletions src/bundler/HTMLImportManifest.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ use bun_collections::VecExt;
use bun_core::strings;
use bun_io::{FmtAdapter, Write};
use bun_js_printer::Encoding;
use bun_paths::resolve_path::relative_normalized;
use bun_paths::resolve_path::{platform, platform_to_posix_in_place, relative_normalized};
use bun_resolver::fs::FileSystem;

use crate::Graph::Graph;
Expand Down Expand Up @@ -197,6 +197,7 @@ pub(crate) fn write<W: Write + ?Sized>(
// Use the server-side public path here.
let public_path: &[u8] = &options.public_path;
let mut temp_buffer: Vec<u8> = Vec::new();
let mut input_buffer: Vec<u8> = Vec::new();

for ch in chunks.iter() {
if ch.entry_point.source_index() == browser_source_index && ch.entry_point.is_entry_point()
Expand Down Expand Up @@ -253,11 +254,11 @@ pub(crate) fn write<W: Write + ?Sized>(
let input: &[u8] = if !ch.entry_point.is_entry_point() {
b""
} else {
let path_for_key = relative_normalized::<bun_paths::platform::Posix, false>(
source_path_relative_to_root(
&mut input_buffer,
root_dir,
sources[ch.entry_point.source_index() as usize].path.text,
);
strings::remove_leading_dot_slash(path_for_key)
)
};

let path: &[u8] = if inject_compiler_filesystem_prefix {
Expand Down Expand Up @@ -308,11 +309,11 @@ pub(crate) fn write<W: Write + ?Sized>(
}
first = false;

let path_for_key = relative_normalized::<bun_paths::platform::Posix, false>(
let path_for_key = source_path_relative_to_root(
&mut input_buffer,
root_dir,
sources[source_index.get() as usize].path.text,
);
let path_for_key = strings::remove_leading_dot_slash(path_for_key);

let path: &[u8] = if inject_compiler_filesystem_prefix {
temp_buffer.clear();
Expand Down Expand Up @@ -379,3 +380,18 @@ pub mod html_import_manifest {
Ok(())
}
}

/// The manifest's `input` / asset-key form of a source path: root-relative, `/`-separated.
fn source_path_relative_to_root<'b>(
buf: &'b mut Vec<u8>,
root_dir: &[u8],
path: &[u8],
) -> &'b [u8] {
buf.clear();
buf.extend_from_slice(strings::remove_leading_dot_slash(relative_normalized::<
platform::Auto,
false,
>(root_dir, path)));
platform_to_posix_in_place(&mut buf[..]);
buf
}
4 changes: 3 additions & 1 deletion src/bundler/bundle_v2.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4363,7 +4363,7 @@ pub mod bv2_impl {
// TODO: outbase
let pathname =
Fs::PathName::init(bun_paths::resolve_path::relative_platform::<
bun_paths::resolve_path::platform::Loose,
bun_paths::resolve_path::platform::Auto,
false,
>(
&self.transpiler.options.root_dir,
Expand Down Expand Up @@ -4393,6 +4393,8 @@ pub mod bv2_impl {
!self.transpiler.options.compile_mode.is_executable(),
)
.expect("oom");
// Like a chunk's `final_rel_path`: `/`-separated on every platform.
bun_paths::resolve_path::platform_to_posix_in_place::<u8>(&mut v);
v.into_boxed_slice()
};

Expand Down
12 changes: 0 additions & 12 deletions src/bundler/linker_context/generateChunksInParallel.rs
Original file line number Diff line number Diff line change
Expand Up @@ -405,18 +405,6 @@ pub(crate) fn generate_chunks_in_parallel<const IS_DEV_SERVER: bool>(
continue;
}

// resolve any /./ and /../ occurrences
// use resolvePosix since we asserted above all seps are '/'
#[cfg(windows)]
if strings::index_of(&rel_path, b"/./").is_some() {
let mut buf = bun_paths::PathBuffer::uninit();
let rel_path_fixed: Box<[u8]> = Box::from(&*path::resolve_path::normalize_buf::<
path::platform::Posix,
>(&rel_path, &mut buf));
chunk.final_rel_path = rel_path_fixed;
continue;
}

// A `./[dir]/…` template with `[dir] == "."` yields `././x.js`,
// which importers of the chunk would copy verbatim.
while let Some(i) = strings::index_of(&rel_path, b"/./") {
Expand Down
2 changes: 1 addition & 1 deletion src/bundler/transpiler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3071,7 +3071,7 @@ impl<'a> Transpiler<'a> {
output: &[u8],
) -> Box<[u8]> {
let rel_to_root = bun_paths::resolve_path::relative_platform::<
bun_paths::resolve_path::platform::Loose,
bun_paths::resolve_path::platform::Auto,
false,
>(&self.options.root_dir, file_path_text);
let pathname = Fs::PathName::init(rel_to_root);
Expand Down
26 changes: 13 additions & 13 deletions src/js_parser/p.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6314,14 +6314,16 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O
}
}

// `bun_paths::fs::Path` lacks a package-name method
// (it lives on the resolver `Path`, which `bun_js_parser` cannot depend on), so
// the slice logic is inlined here. Mirrors `src/resolver/fs.rs::Path::packageName`.
/// The unscoped npm package of a specifier (`react/x`) or path (`node_modules<sep>react<sep>x.js`).
fn path_package_name<'a>(path: &fs::Path<'a>) -> Option<&'a [u8]> {
let mut name_to_use = path.pretty;
if let Some(node_modules) = strings::last_index_of(path.text, bun_paths::NODE_MODULES_NEEDLE) {
name_to_use = &path.text[node_modules + bun_paths::NODE_MODULES_NEEDLE.len()..];
}
let (name_to_use, separators): (&[u8], &[u8]) =
match strings::last_index_of(path.text, bun_paths::NODE_MODULES_NEEDLE) {
Some(node_modules) => (
&path.text[node_modules + bun_paths::NODE_MODULES_NEEDLE.len()..],
if cfg!(windows) { b"/\\" } else { b"/" },
),
None => (path.pretty, b"/"),
};

let pkgname = {
let str = name_to_use;
Expand All @@ -6330,17 +6332,15 @@ fn path_package_name<'a>(path: &fs::Path<'a>) -> Option<&'a [u8]> {
break 'brk str;
}
if str[0] == b'@' {
if let Some(first_slash) = strings::index_of_char(&str[1..], b'/') {
let first_slash = first_slash as usize;
if let Some(first_slash) = strings::index_of_any(&str[1..], separators) {
let remainder = &str[1 + first_slash + 1..];
if let Some(last_slash) = strings::index_of_char(remainder, b'/') {
let last_slash = last_slash as usize;
if let Some(last_slash) = strings::index_of_any(remainder, separators) {
break 'brk &str[0..first_slash + 1 + last_slash + 1];
}
}
}
if let Some(first_slash) = strings::index_of_char(str, b'/') {
break 'brk &str[0..first_slash as usize];
if let Some(first_slash) = strings::index_of_any(str, separators) {
break 'brk &str[0..first_slash];
}
str
}
Expand Down
2 changes: 1 addition & 1 deletion src/resolver/package_json.rs
Original file line number Diff line number Diff line change
Expand Up @@ -282,7 +282,7 @@ trait FileSystemPackageJsonExt {
}
impl FileSystemPackageJsonExt for crate::fs::FileSystem {
fn join(&self, parts: &[&[u8]]) -> &'static [u8] {
resolve_path::resolve_path::join::<resolve_path::resolve_path::platform::Loose>(parts)
resolve_path::resolve_path::join::<resolve_path::platform::Auto>(parts)
}
}

Expand Down
26 changes: 5 additions & 21 deletions src/standalone_graph/StandaloneModuleGraph.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1143,24 +1143,8 @@ fn encode_text_module(
}

/// The embedded bunfs key for an output file, relative to the prefix.
///
/// Windows: store the key with `/`. The template printer emits native
/// `\` into `dest_path`, but `find_assume_standalone_path` normalizes
/// lookups to `/`, so a `\` key would miss (ENOENT). `src/bundler/Chunk.rs`
/// only normalizes a scratch copy, so we re-normalize here.
fn module_dest_path(output_file: &OutputFile) -> std::borrow::Cow<'_, [u8]> {
let dest_path = bun_core::strings::remove_leading_dot_slash(&output_file.dest_path);
#[cfg(windows)]
{
let mut buf = bun_paths::path_buffer_pool::get();
std::borrow::Cow::Owned(
path::resolve_path::platform_to_posix_buf::<u8>(dest_path, &mut buf).to_vec(),
)
}
#[cfg(not(windows))]
{
std::borrow::Cow::Borrowed(dest_path)
}
fn module_dest_path(output_file: &OutputFile) -> &[u8] {
bun_core::strings::remove_leading_dot_slash(&output_file.dest_path)
}

pub(crate) fn to_bytes(
Expand Down Expand Up @@ -1244,7 +1228,7 @@ pub(crate) fn to_bytes(

// Same `[name]` and `[hash]` means the same bytes: keep the first copy.
if seen_paths
.get_or_put(&module_dest_path(output_file))?
.get_or_put(module_dest_path(output_file))?
.found_existing
{
continue;
Expand Down Expand Up @@ -1337,7 +1321,7 @@ pub(crate) fn to_bytes(

if Environment::IS_CANARY || Environment::IS_DEBUG {
if let Some(dump_code_dir) = bun_core::env_var::BUN_FEATURE_FLAG_DUMP_CODE.get() {
let dest_path = &*module_dest_path(output_file);
let dest_path = module_dest_path(output_file);
// `dest_path` keeps `..` for the embedded bunfs key below; neutralize
// every `..` segment here so the on-disk dump can't escape
// `dump_code_dir` (the join would otherwise normalize `..` above it).
Expand Down Expand Up @@ -1455,7 +1439,7 @@ pub(crate) fn to_bytes(
module.name = string_builder.fmt_append_count_z(format_args!(
"{}{}",
bstr::BStr::new(prefix),
bstr::BStr::new(&*module_dest_path(output_file))
bstr::BStr::new(module_dest_path(output_file))
));
// The bytecode cache was generated under the bytecode output file's
// path; the runtime must present exactly the same path to hit it.
Expand Down
2 changes: 1 addition & 1 deletion test/bundler/bundler_barrel.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1676,7 +1676,7 @@ describe("bundler", () => {
outdir: "/out",
// Plugin transforms a.js content
plugins(builder) {
builder.onLoad({ filter: /loadlib\/a\.js$/ }, () => {
builder.onLoad({ filter: /loadlib[\/\\]a\.js$/ }, () => {
return { contents: 'export const A = "transformed-by-plugin";', loader: "js" };
});
},
Expand Down
3 changes: 2 additions & 1 deletion test/bundler/bundler_compile.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -654,7 +654,8 @@ describe("bundler", () => {
stdout:
process.platform !== "win32"
? `file:///$bunfs/root/out /$bunfs/root/out`
: `file:///B:/~BUN/root/out B:\\~BUN\\root\\out`,
: // pathToFileURL percent-encodes '~' (matches Node.js)
`file:///B:/%7EBUN/root/out B:\\~BUN\\root\\out`,
setCwd: true,
},
});
Expand Down
3 changes: 3 additions & 0 deletions test/bundler/bundler_edgecase.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2765,6 +2765,8 @@ describe("bundler", () => {
...deepChainFiles,
},
backend: "cli",
// (local runs: writing 7000 fixture files is slow on Windows; the build itself is well under a second)
timeoutScale: 6,
run: { stdout: String(deepChainDepth) },
});
// Top-level await in the entry makes `validate_tla` / `propagate_async` walk
Expand All @@ -2778,6 +2780,7 @@ describe("bundler", () => {
...deepChainFiles,
},
backend: "cli",
timeoutScale: 6,
Comment thread
dylan-conway marked this conversation as resolved.
Resolved
onAfterBundle(api) {
const out = api.readFile("out.js");
expect(out).toContain(`init_m${deepChainDepth - 2}`);
Expand Down
15 changes: 11 additions & 4 deletions test/bundler/bundler_env.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,22 +27,29 @@ for (let backend of ["api", "cli"] as const) {
},
});

// A variable from the process environment (not a .env file) is inlined at build time. It has to be one this
// process started with (the api backend builds in-process), spelled as the environment spells it (on Windows the
// inlined name is case-sensitive even though process.env is not: `Path`, not `PATH`).
const systemKey = ["HOME", "OS", "NUMBER_OF_PROCESSORS", "COMPUTERNAME", "USER", "LANG"].find(
key => process.env[key] && /^[\x20-\x7e]+$/.test(process.env[key]!) && !/["`$\\]/.test(process.env[key]!),
)!;
const systemValue = process.env[systemKey]!;
itBundled("env/inline system", {
env: {
PATH: process.env.PATH,
[systemKey]: systemValue,
},
backend: backend,
dotenv: "inline",
files: {
"/a.js": `
console.log(process.env.PATH);
console.log(process.env.${systemKey});
`,
},
run: {
env: {
PATH: "/fail",
[systemKey]: "the run-time value, which inlining should have replaced",
},
stdout: process.env.PATH + "\n",
stdout: systemValue + "\n",
},
});

Expand Down
4 changes: 3 additions & 1 deletion test/bundler/bundler_jsx.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -115,9 +115,10 @@ function itBundledDevAndProd(
prodTodo?: boolean;
},
) {
const { devStdout, prodStdout, ...rest } = opts;
const { devStdout, prodStdout, devTodo, prodTodo, ...rest } = opts;
itBundled(id + "Dev", {
...rest,
todo: rest.todo || devTodo,
env: {
NODE_ENV: "development",
},
Expand All @@ -130,6 +131,7 @@ function itBundledDevAndProd(
});
itBundled(id + "Prod", {
...rest,
todo: rest.todo || prodTodo,
env: {
NODE_ENV: "production",
},
Expand Down
9 changes: 6 additions & 3 deletions test/bundler/bundler_loader.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -407,9 +407,12 @@ describe("bundler", async () => {
},
});

const moon = await Bun.file(
fileURLToPath(import.meta.resolve("../js/bun/util/text-loader-fixture-text-file.backslashes.txt")),
).text();
// (a Windows checkout may have given the fixture CRLF line endings; the harness compares LF-normalized output)
const moon = (
await Bun.file(
fileURLToPath(import.meta.resolve("../js/bun/util/text-loader-fixture-text-file.backslashes.txt")),
).text()
).replaceAll("\r\n", "\n");

// https://github.com/oven-sh/bun/issues/3449
itBundled("bun/loader-text-file-#3449", {
Expand Down
4 changes: 2 additions & 2 deletions test/bundler/bundler_plugin.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -259,7 +259,7 @@ describe("bundler", () => {
});
builder.onResolve({ filter: /^magic:.*/ }, args => {
expect(args.path).toBe("magic:some_string");
expect(args.importer).toBe(root + "/index.ts");
expect(args.importer).toBe(path.join(root, "index.ts"));
expect(args.namespace).toBe("file");
expect(args.kind).toBe("import-statement");
onResolveCount++;
Expand Down Expand Up @@ -537,7 +537,7 @@ describe("bundler", () => {
stdout: "this string should exist once this string should exist once",
},
onAfterBundle(api) {
expect(importers.sort()).toEqual([root + "/one.ts", root + "/two.ts"].sort());
expect(importers.sort()).toEqual([path.join(root, "one.ts"), path.join(root, "two.ts")].sort());
expect(onResolveCount).toBe(2);
const contents = api.readFile("/out.js");
expect([...contents.matchAll(/this string should exist once/g)].length).toBe(1);
Expand Down
10 changes: 6 additions & 4 deletions test/bundler/compile-argv.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,8 +95,9 @@ describe("bundler", () => {
}

// argv[1] should be the script path (contains the bundle path)
if (!process.argv[1].includes("bunfs")) {
console.error("FAIL: Expected argv[1] to contain 'bunfs' path, got", process.argv[1]);
// Windows uses "B:/~BUN/root/", Unix uses "/$bunfs/root/"
if (!process.argv[1].includes("/$bunfs/") && !process.argv[1].includes("/~BUN/")) {
console.error("FAIL: Expected argv[1] to contain standalone bundle root, got", process.argv[1]);
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Resolved
process.exit(1);
}

Expand Down Expand Up @@ -152,8 +153,9 @@ describe("bundler", () => {
process.exit(1);
}

if (!process.argv[1].includes("bunfs")) {
console.error("FAIL: Expected argv[1] to contain 'bunfs' path, got", process.argv[1]);
// Windows uses "B:/~BUN/root/", Unix uses "/$bunfs/root/"
if (!process.argv[1].includes("/$bunfs/") && !process.argv[1].includes("/~BUN/")) {
console.error("FAIL: Expected argv[1] to contain standalone bundle root, got", process.argv[1]);
process.exit(1);
}

Expand Down
Loading
Loading