mirror of
https://github.com/oven-sh/bun
synced 2026-02-10 19:08:50 +00:00
Fix Windows path issues with tarball URLs containing query parameters
Fixes #20647 - Strip query parameters from tarball names when creating temp directories on Windows - Add Windows-specific handling to remove slashes from URL paths - Prevent "BadPathName" and "ENOTEMPTY" errors when installing tarballs with query params Added comprehensive tests for installing tarballs with query parameters to ensure they install successfully on Windows. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -764,6 +764,7 @@ const Output = bun.Output;
|
||||
const Path = bun.path;
|
||||
const Progress = bun.Progress;
|
||||
const default_allocator = bun.default_allocator;
|
||||
const strings = bun.strings;
|
||||
const Command = bun.cli.Command;
|
||||
const File = bun.sys.File;
|
||||
|
||||
|
||||
@@ -121,6 +121,12 @@ fn extract(this: *const ExtractTarball, log: *logger.Log, tgz_bytes: []const u8)
|
||||
const basename = brk: {
|
||||
var tmp = name;
|
||||
|
||||
// Strip query parameters from the name (e.g., "?token=abc" from "package.tgz?token=abc")
|
||||
// This is essential on Windows where '?' is an invalid path character
|
||||
if (strings.indexOfChar(tmp, '?')) |query_index| {
|
||||
tmp = tmp[0..query_index];
|
||||
}
|
||||
|
||||
// Handle URLs - extract just the filename from the URL
|
||||
if (strings.hasPrefixComptime(tmp, "https://") or strings.hasPrefixComptime(tmp, "http://")) {
|
||||
tmp = std.fs.path.basename(tmp);
|
||||
@@ -130,15 +136,20 @@ fn extract(this: *const ExtractTarball, log: *logger.Log, tgz_bytes: []const u8)
|
||||
} else if (strings.endsWithComptime(tmp, ".tar.gz")) {
|
||||
tmp = tmp[0 .. tmp.len - 7];
|
||||
}
|
||||
} else if (tmp[0] == '@') {
|
||||
} else if (tmp.len > 0 and tmp[0] == '@') {
|
||||
if (strings.indexOfChar(tmp, '/')) |i| {
|
||||
tmp = tmp[i + 1 ..];
|
||||
if (tmp.len > i + 1) {
|
||||
tmp = tmp[i + 1 ..];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (comptime Environment.isWindows) {
|
||||
// On Windows, colons are invalid in paths (except for drive letters)
|
||||
// URLs like "https://example.com/package.tgz" would have a colon
|
||||
if (strings.lastIndexOfChar(tmp, ':')) |i| {
|
||||
tmp = tmp[i + 1 ..];
|
||||
if (i > "C:".len)
|
||||
tmp = tmp[i + 1 ..];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2428,3 +2428,57 @@ it("should install tarball with tarball dependencies", async () => {
|
||||
await access(join(add_dir, "node_modules", "test-parent"));
|
||||
await access(join(add_dir, "node_modules", "test-child"));
|
||||
});
|
||||
|
||||
it("should install tarball with query parameters", async () => {
|
||||
// Regression test for issue #20647
|
||||
// Previously on Windows, tarball URLs with query parameters would fail with BadPathName errors
|
||||
|
||||
// Use a local server to serve the tarball
|
||||
using server = Bun.serve({
|
||||
port: 0,
|
||||
fetch(req) {
|
||||
// Serve the same tarball regardless of query parameters
|
||||
return new Response(Bun.file(join(__dirname, "baz-0.0.3.tgz")));
|
||||
},
|
||||
});
|
||||
const server_url = server.url.href.replace(/\/+$/, "");
|
||||
|
||||
await writeFile(
|
||||
join(package_dir, "package.json"),
|
||||
JSON.stringify({
|
||||
name: "foo",
|
||||
version: "0.0.1",
|
||||
}),
|
||||
);
|
||||
|
||||
// Add a tarball with query parameters (used for auth tokens, cache busting, etc.)
|
||||
const tarballUrl = `${server_url}/package.tgz?token=abc123×tamp=2024`;
|
||||
const { stdout, stderr, exited } = spawn({
|
||||
cmd: [bunExe(), "add", tarballUrl],
|
||||
cwd: package_dir,
|
||||
stdout: "pipe",
|
||||
stdin: "pipe",
|
||||
stderr: "pipe",
|
||||
env,
|
||||
});
|
||||
|
||||
const err = await stderr.text();
|
||||
expect(err).toContain("Saved lockfile");
|
||||
const out = await stdout.text();
|
||||
expect(out).toContain("installed baz@");
|
||||
expect(await exited).toBe(0);
|
||||
|
||||
// Verify the package was actually installed
|
||||
expect(await readdirSorted(join(package_dir, "node_modules"))).toContain("baz");
|
||||
expect(await file(join(package_dir, "node_modules", "baz", "package.json")).json()).toEqual({
|
||||
name: "baz",
|
||||
version: "0.0.3",
|
||||
bin: {
|
||||
"baz-run": "index.js",
|
||||
},
|
||||
});
|
||||
|
||||
// Verify package.json has the dependency with the full URL including query params
|
||||
const pkg = await file(join(package_dir, "package.json")).json();
|
||||
expect(pkg.dependencies["baz"]).toBe(tarballUrl);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user