Skip to content
Open
Show file tree
Hide file tree
Changes from 5 commits
Commits
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
49 changes: 39 additions & 10 deletions src/bundler/bundle_v2.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4122,24 +4122,53 @@
let source = &mut sources[index];

let output_path: Box<[u8]> = {
// TODO: outbase
let pathname =
Fs::PathName::init(bun_paths::resolve_path::relative_platform::<
bun_paths::resolve_path::platform::Loose,
false,
>(
&self.transpiler.options.root_dir,
source.path.text,
));
let pathname = Fs::PathName::init(source.path.text);

template.placeholder.name = pathname.base.to_vec().into_boxed_slice();
template.placeholder.dir = pathname.dir.to_vec().into_boxed_slice();
let mut ext: &[u8] = pathname.ext;
if !ext.is_empty() && ext[0] == b'.' {
ext = &ext[1..];
}
template.placeholder.ext = ext.to_vec().into_boxed_slice();

if template.needs(options::PlaceholderField::Dir) {
// `root_dir` is already canonical (`get_fd_path`);
// canonicalize the source dir the same way so
// Windows 8.3 short names relativize correctly.
let source_dir: &[u8] = if pathname.dir.is_empty() {
b"."
} else {
pathname.dir
};
let mut real_path_buf = bun_paths::path_buffer_pool::get();
let dir: &[u8] = 'dir: {
let Ok(dir_file) = bun_sys::File::openat(
bun_sys::Fd::cwd(),
source_dir,
bun_sys::O::PATH | bun_sys::O::DIRECTORY,
0,
) else {
break 'dir &*bun_paths::resolve_path::normalize_buf::<
bun_paths::platform::Auto,
>(
source_dir, &mut real_path_buf.0
);
};
match dir_file.get_path(&mut real_path_buf) {
Ok(p) => p,
Err(_) => &*bun_paths::resolve_path::normalize_buf::<
bun_paths::platform::Auto,
>(
source_dir, &mut real_path_buf.0
),
}
};
template.placeholder.dir = bun_paths::resolve_path::relative_alloc(
&self.transpiler.options.root_dir,
dir,

Check warning on line 4168 in src/bundler/bundle_v2.rs

View check run for this annotation

Claude / Claude Code Review

Duplicated dir-canonicalization block; extract shared helper

This ~30-line `needs(Dir)` → `openat(cwd, …, O::PATH|O::DIRECTORY)` → `get_path` → `normalize_buf` fallback → `relative_alloc(root_dir, …)` block is a near-verbatim copy of `src/bundler/linker_context/computeChunks.rs:636-676` (the PR description says it "mirrors compute_chunks"). REVIEW.md's *one implementation, in the right place* rule suggests extracting a shared helper (e.g. on `options::PathTemplate` or in `bun_paths`) so the two `[dir]` resolution paths can't drift — they already differ: `
Comment thread
robobun marked this conversation as resolved.
Outdated
)?;
}

if template.needs(options::PlaceholderField::Hash) {
template.placeholder.hash =
Some(content_hashes_for_additional_files[index]);
Expand Down
65 changes: 64 additions & 1 deletion test/bundler/bundler_naming.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
import { describe } from "bun:test";
import { describe, expect, test } from "bun:test";
import { bunEnv, bunExe, isWindows, tempDir } from "harness";
import { symlinkSync } from "node:fs";
import { join } from "node:path";
import { ESBUILD, itBundled } from "./expectBundled";

describe("bundler", () => {
Expand Down Expand Up @@ -191,6 +194,66 @@ describe("bundler", () => {
},
],
});
// assetNaming `[dir]` must be relative to the configured root even when
// the root directory and the asset's source path spell the same on-disk
// location differently. Bun canonicalizes `root` via the file descriptor
// (`GetFinalPathNameByHandle` on Windows, /proc/self/fd on Linux) but
// `Bun.build({ files })` source paths are the literal map keys; previously
// the uncanonicalized source path was relativized against the canonical
// root, so no common prefix was found and `[dir]` expanded to a long
// `_.._/_.._/...` traversal back into the source tree. On Windows this
// surfaced whenever the cwd contained an 8.3 short path component such as
// `C:\Users\RUNNER~1\...` (the default TEMP directory in CI).
Comment thread
robobun marked this conversation as resolved.
Outdated
test("naming/AssetNamingDirCanonicalRoot", async () => {
using base = tempDir("asset-naming-dir-canon", {
"real/src/lib/first/.keep": "",
"real/src/lib/second/.keep": "",
});
const real = join(String(base), "real");
const link = join(String(base), "project-link");
// A junction needs no elevation on Windows; on POSIX this is a plain
// directory symlink. Either way `root` below canonicalizes to `real/src`
// while the `files` map keys keep the `project-link` spelling.
Comment thread
robobun marked this conversation as resolved.
symlinkSync(real, link, isWindows ? "junction" : "dir");

const entry = join(link, "src/lib/first/file.js").replaceAll("\\", "/");
const asset = join(link, "src/lib/second/data.file").replaceAll("\\", "/");
const root = join(link, "src");

const script = `
const result = await Bun.build({
entrypoints: [${JSON.stringify(entry)}],
files: {
${JSON.stringify(entry)}: 'import f from "../second/data.file"; console.log(f);',
${JSON.stringify(asset)}: "this is a file",
},
root: ${JSON.stringify(root)},
naming: { entry: "hello.[ext]", asset: "[dir]/test.[ext]" },
loader: { ".file": "file" },
});
if (!result.success) {
for (const m of result.logs) console.error(String(m));
process.exit(1);
}
for (const out of result.outputs) console.log(out.kind + " " + out.path);
`;

await using proc = Bun.spawn({
cmd: [bunExe(), "-e", script],
env: bunEnv,
cwd: String(base),
stdout: "pipe",
stderr: "pipe",
});
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);

expect(stderr).toBe("");
Comment thread
robobun marked this conversation as resolved.
Outdated
const out = stdout.replaceAll("\\", "/");
const assetLine = out.split("\n").find(l => l.startsWith("asset "));
expect(assetLine).toBe("asset ./lib/second/test.file");
expect(out).not.toContain("_.._");
expect(exitCode).toBe(0);
});
itBundled("naming/AssetNoOverwrite", {
todo: true,
files: {
Expand Down
Loading