Compare commits

...

1 Commits

Author SHA1 Message Date
Claude Bot
740ae6ea31 fix(install): resolve catalog file: paths relative to root, not workspace
When a workspace package references a catalog entry containing a
`file:./...` path, the path was incorrectly resolved relative to the
workspace directory instead of the root package.json where the catalog
is defined.

This fix adds a check in the local tarball extraction code: if the
dependency's version tag is `.catalog`, the tarball path is resolved
relative to the root directory, preserving the correct behavior for
catalog entries.

Fixes #25752

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-12 12:04:55 +00:00
2 changed files with 80 additions and 1 deletions

View File

@@ -231,7 +231,8 @@ pub fn callback(task: *ThreadPool.Task) void {
this.status = Status.success;
},
.local_tarball => {
const workspace_pkg_id = manager.lockfile.getWorkspacePkgIfWorkspaceDep(this.request.local_tarball.tarball.dependency_id);
const dependency_id = this.request.local_tarball.tarball.dependency_id;
const workspace_pkg_id = manager.lockfile.getWorkspacePkgIfWorkspaceDep(dependency_id);
var abs_buf: bun.PathBuffer = undefined;
const tarball_path, const normalize = if (workspace_pkg_id != invalid_package_id) tarball_path: {
@@ -239,6 +240,11 @@ pub fn callback(task: *ThreadPool.Task) void {
if (workspace_res.tag != .workspace) break :tarball_path .{ this.request.local_tarball.tarball.url.slice(), true };
// If the dependency originated from a catalog entry, the tarball path should be
// resolved relative to the root (where catalogs are defined), not the workspace.
const dependency: Dependency = manager.lockfile.buffers.dependencies.items[dependency_id];
if (dependency.version.tag == .catalog) break :tarball_path .{ this.request.local_tarball.tarball.url.slice(), true };
// Construct an absolute path to the tarball.
// Normally tarball paths are always relative to the root directory, but if a
// workspace depends on a tarball path, it should be relative to the workspace.
@@ -352,6 +358,7 @@ const string = []const u8;
const std = @import("std");
const install = @import("./install.zig");
const Dependency = install.Dependency;
const DependencyID = install.DependencyID;
const ExtractData = install.ExtractData;
const ExtractTarball = install.ExtractTarball;

View File

@@ -0,0 +1,72 @@
// https://github.com/oven-sh/bun/issues/25752
// Catalog entries with `file:./...` paths should resolve relative to the root
// package.json (where catalogs are defined), not the workspace that references them.
import { file } from "bun";
import { expect, test } from "bun:test";
import { bunEnv, bunExe, pack, tempDir } from "harness";
import { join } from "path";
test("catalog file: paths resolve relative to root, not workspace", async () => {
// First create a simple package and pack it to get a tarball
using pkgDir = tempDir("pkg-for-tarball", {
"package.json": JSON.stringify({
name: "catalog-pkg",
version: "1.0.0",
}),
"index.js": "module.exports = 'catalog-pkg';",
});
await pack(String(pkgDir), bunEnv);
const tarballContent = await file(join(String(pkgDir), "catalog-pkg-1.0.0.tgz")).arrayBuffer();
// Create the monorepo structure
using monorepoDir = tempDir("monorepo-25752", {
"package.json": JSON.stringify({
name: "my-monorepo",
workspaces: ["packages/*"],
catalogs: {
vendored: {
"catalog-pkg": "file:./vendored/catalog-pkg-1.0.0.tgz",
},
},
}),
vendored: {
"catalog-pkg-1.0.0.tgz": new Uint8Array(tarballContent),
},
packages: {
"my-app": {
"package.json": JSON.stringify({
name: "my-app",
dependencies: {
"catalog-pkg": "catalog:vendored",
},
}),
},
},
});
// Run bun install
await using proc = Bun.spawn({
cmd: [bunExe(), "install"],
cwd: String(monorepoDir),
env: bunEnv,
stdout: "pipe",
stderr: "pipe",
});
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
// The tarball should be resolved relative to the root (where catalogs is defined),
// not relative to packages/my-app where the dependency is declared
expect(stderr).not.toContain("ENOENT");
expect(stderr).not.toContain("failed to resolve");
expect(exitCode).toBe(0);
// Verify the package was installed correctly in the workspace's node_modules
const installedPkg = await file(
join(String(monorepoDir), "packages", "my-app", "node_modules", "catalog-pkg", "package.json"),
).json();
expect(installedPkg.name).toBe("catalog-pkg");
expect(installedPkg.version).toBe("1.0.0");
});