diff --git a/cmake/targets/BuildHdrHistogram.cmake b/cmake/targets/BuildHdrHistogram.cmake index cee0998e7e..855684dfd9 100644 --- a/cmake/targets/BuildHdrHistogram.cmake +++ b/cmake/targets/BuildHdrHistogram.cmake @@ -4,7 +4,7 @@ register_repository( REPOSITORY HdrHistogram/HdrHistogram_c COMMIT - 8dcce8f68512fca460b171bccc3a5afce0048779 + be60a9987ee48d0abf0d7b6a175bad8d6c1585d1 ) register_cmake_command( diff --git a/src/install/PackageManager/PackageManagerEnqueue.zig b/src/install/PackageManager/PackageManagerEnqueue.zig index b69aadf0fb..df1e6c8262 100644 --- a/src/install/PackageManager/PackageManagerEnqueue.zig +++ b/src/install/PackageManager/PackageManagerEnqueue.zig @@ -446,7 +446,6 @@ pub fn enqueueDependencyWithMainAndSuccessFn( if (dependency.behavior.isOptionalPeer()) return; var name = dependency.realname(); - var name_hash = switch (dependency.version.tag) { .dist_tag, .git, .github, .npm, .tarball, .workspace => String.Builder.stringHash(this.lockfile.str(&name)), else => dependency.name_hash, diff --git a/src/install/extract_tarball.zig b/src/install/extract_tarball.zig index 2aacff7581..aefbfb8eea 100644 --- a/src/install/extract_tarball.zig +++ b/src/install/extract_tarball.zig @@ -120,7 +120,17 @@ fn extract(this: *const ExtractTarball, log: *logger.Log, tgz_bytes: []const u8) }; const basename = brk: { var tmp = name; - if (tmp[0] == '@') { + + // 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); + // Remove .tgz or .tar.gz extension if present + if (strings.endsWithComptime(tmp, ".tgz")) { + tmp = tmp[0 .. tmp.len - 4]; + } else if (strings.endsWithComptime(tmp, ".tar.gz")) { + tmp = tmp[0 .. tmp.len - 7]; + } + } else if (tmp[0] == '@') { if (strings.indexOfChar(tmp, '/')) |i| { tmp = tmp[i + 1 ..]; } diff --git a/test/cli/install/bun-add.test.ts b/test/cli/install/bun-add.test.ts index 1dbec2087e..c6b0785ba2 100644 --- a/test/cli/install/bun-add.test.ts +++ b/test/cli/install/bun-add.test.ts @@ -2324,3 +2324,94 @@ it("should add multiple dependencies specified on command line", async () => { }); await access(join(package_dir, "bun.lockb")); }); + +it("should install tarball with tarball dependencies", async () => { + // This test verifies that tarballs containing dependencies that are also tarballs + // can be installed correctly. Regression test for URL corruption bug where + // URLs like https://example.com/pkg.tgz get mangled with cache folder patterns. + + // Create simple test tarballs + const tmpDir = tmpdirSync(); + + // Create child package + const childDir = join(tmpDir, "child"); + await mkdir(childDir, { recursive: true }); + await writeFile(join(childDir, "package.json"), JSON.stringify({ name: "test-child", version: "1.0.0" })); + + // Create child tarball + const { exited: childTarExited } = spawn({ + cmd: ["tar", "-czf", join(tmpDir, "child.tgz"), "-C", tmpDir, "child"], + stdout: "pipe", + stderr: "pipe", + }); + expect(await childTarExited).toBe(0); + + // Set up server first to get the port + using server = Bun.serve({ + port: 0, + fetch(req) { + const url = new URL(req.url); + if (url.pathname === "/child.tgz") { + return new Response(Bun.file(join(tmpDir, "child.tgz"))); + } else if (url.pathname === "/parent.tgz") { + return new Response(Bun.file(join(tmpDir, "parent.tgz"))); + } + return new Response("Not found", { status: 404 }); + }, + }); + + const server_url = server.url.href.replace(/\/+$/, ""); + + // Create parent package that depends on child via URL + const parentDir = join(tmpDir, "parent"); + await mkdir(parentDir, { recursive: true }); + await writeFile( + join(parentDir, "package.json"), + JSON.stringify({ + name: "test-parent", + version: "1.0.0", + dependencies: { + "test-child": `${server_url}/child.tgz`, + }, + }), + ); + + // Create parent tarball + const { exited: parentTarExited } = spawn({ + cmd: ["tar", "-czf", join(tmpDir, "parent.tgz"), "-C", tmpDir, "parent"], + stdout: "pipe", + stderr: "pipe", + }); + expect(await parentTarExited).toBe(0); + + // Now test adding the parent tarball + await writeFile( + join(add_dir, "package.json"), + JSON.stringify({ + name: "foo", + }), + ); + + const urls: string[] = []; + setHandler(dummyRegistry(urls)); + + const { stdout, stderr, exited } = spawn({ + cmd: [bunExe(), "add", `${server_url}/parent.tgz`], + cwd: add_dir, + stdout: "pipe", + stdin: "pipe", + stderr: "pipe", + env, + }); + + const err = await new Response(stderr).text(); + expect(err).not.toContain("error:"); + expect(err).not.toContain("HttpNotFound"); + expect(err).not.toContain("404"); + + expect(await exited).toBe(0); + + // Verify both packages were installed + await access(join(add_dir, "node_modules", "test-parent")); + await access(join(add_dir, "node_modules", "test-child")); +});