fix(install): resolve dist-tag dependencies to local workspace packages

When a workspace package has the same name as an npm package and a
dependency uses a dist-tag specifier (e.g. "latest" or ""), bun install
would incorrectly fetch from the npm registry instead of symlinking to
the local workspace. This extends the workspace resolution logic in both
the lockfile package resolver and the enqueue step to handle dist-tag
versions the same way as other version specifiers, preferring local
workspace packages when `link_workspace_packages` is enabled.
This commit is contained in:
Jarred Sumner
2026-01-29 08:35:01 +01:00
parent 4feede90f5
commit b57f3bfec1
4 changed files with 296 additions and 359 deletions

View File

@@ -1611,6 +1611,31 @@ fn getOrPutResolvedPackage(
}
}
}
} else if (version.tag == .dist_tag) {
if (this.options.link_workspace_packages) {
const workspace_path = if (this.lockfile.workspace_paths.count() > 0)
this.lockfile.workspace_paths.get(name_hash)
else
null;
if (workspace_path != null) {
const root_package = this.lockfile.rootPackage() orelse break :resolve_from_workspace;
const root_dependencies = root_package.dependencies.get(this.lockfile.buffers.dependencies.items);
const root_resolutions = root_package.resolutions.get(this.lockfile.buffers.resolutions.items);
for (root_dependencies, root_resolutions) |root_dep, workspace_package_id| {
if (workspace_package_id != invalid_package_id and
root_dep.version.tag == .workspace and
root_dep.name_hash == name_hash)
{
successFn(this, dependency_id, workspace_package_id);
return .{
.package = this.lockfile.packages.get(workspace_package_id),
.is_first_time = false,
};
}
}
}
}
}
}

View File

@@ -1121,6 +1121,24 @@ pub fn Package(comptime SemverIntType: type) type {
}
}
},
.dist_tag => {
if (workspace_path != null and pm.options.link_workspace_packages) {
const path = workspace_path.?.sliced(buf);
if (Dependency.parseWithTag(
allocator,
external_alias.value,
external_alias.hash,
path.slice,
.workspace,
&path,
log,
pm,
)) |dep| {
dependency_version.tag = dep.tag;
dependency_version.value = dep.value;
}
}
},
.workspace => workspace: {
if (workspace_path) |path| {
if (workspace_range) |range| {

View File

@@ -738,6 +738,252 @@ exports[`dependency on workspace without version in package.json: version: *-pre
}"
`;
exports[`dependency on workspace without version in package.json: version: latest 1`] = `
"{
"format": "v3",
"meta_hash": "0000000000000000000000000000000000000000000000000000000000000000",
"package_index": {
"no-deps": 2,
"foo": 0,
"bar": 1
},
"trees": [
{
"id": 0,
"path": "node_modules",
"depth": 0,
"dependencies": {
"bar": {
"id": 0,
"package_id": 1
},
"no-deps": {
"id": 1,
"package_id": 2
}
}
}
],
"dependencies": [
{
"name": "bar",
"literal": "",
"workspace": "packages/bar",
"package_id": 1,
"behavior": {
"workspace": true
},
"id": 0
},
{
"name": "no-deps",
"literal": "",
"workspace": "packages/mono",
"package_id": 2,
"behavior": {
"workspace": true
},
"id": 1
},
{
"name": "no-deps",
"literal": "latest",
"workspace": "packages/mono",
"package_id": 2,
"behavior": {
"prod": true
},
"id": 2
}
],
"packages": [
{
"id": 0,
"name": "foo",
"name_hash": "14841791273925386894",
"resolution": {
"tag": "root",
"value": "",
"resolved": ""
},
"dependencies": [
0,
1
],
"integrity": null,
"man_dir": "",
"origin": "local",
"bin": null,
"scripts": {}
},
{
"id": 1,
"name": "bar",
"name_hash": "11592711315645265694",
"resolution": {
"tag": "workspace",
"value": "workspace:packages/bar",
"resolved": "workspace:packages/bar"
},
"dependencies": [
2
],
"integrity": null,
"man_dir": "",
"origin": "npm",
"bin": null,
"scripts": {}
},
{
"id": 2,
"name": "no-deps",
"name_hash": "5128161233225832376",
"resolution": {
"tag": "workspace",
"value": "workspace:packages/mono",
"resolved": "workspace:packages/mono"
},
"dependencies": [],
"integrity": null,
"man_dir": "",
"origin": "npm",
"bin": null,
"scripts": {}
}
],
"workspace_paths": {
"11592711315645265694": "packages/bar",
"5128161233225832376": "packages/mono"
},
"workspace_versions": {
"11592711315645265694": "1.0.0"
}
}"
`;
exports[`dependency on workspace without version in package.json: version: 1`] = `
"{
"format": "v3",
"meta_hash": "0000000000000000000000000000000000000000000000000000000000000000",
"package_index": {
"no-deps": 2,
"foo": 0,
"bar": 1
},
"trees": [
{
"id": 0,
"path": "node_modules",
"depth": 0,
"dependencies": {
"bar": {
"id": 0,
"package_id": 1
},
"no-deps": {
"id": 1,
"package_id": 2
}
}
}
],
"dependencies": [
{
"name": "bar",
"literal": "",
"workspace": "packages/bar",
"package_id": 1,
"behavior": {
"workspace": true
},
"id": 0
},
{
"name": "no-deps",
"literal": "",
"workspace": "packages/mono",
"package_id": 2,
"behavior": {
"workspace": true
},
"id": 1
},
{
"name": "no-deps",
"literal": "",
"workspace": "packages/mono",
"package_id": 2,
"behavior": {
"prod": true
},
"id": 2
}
],
"packages": [
{
"id": 0,
"name": "foo",
"name_hash": "14841791273925386894",
"resolution": {
"tag": "root",
"value": "",
"resolved": ""
},
"dependencies": [
0,
1
],
"integrity": null,
"man_dir": "",
"origin": "local",
"bin": null,
"scripts": {}
},
{
"id": 1,
"name": "bar",
"name_hash": "11592711315645265694",
"resolution": {
"tag": "workspace",
"value": "workspace:packages/bar",
"resolved": "workspace:packages/bar"
},
"dependencies": [
2
],
"integrity": null,
"man_dir": "",
"origin": "npm",
"bin": null,
"scripts": {}
},
{
"id": 2,
"name": "no-deps",
"name_hash": "5128161233225832376",
"resolution": {
"tag": "workspace",
"value": "workspace:packages/mono",
"resolved": "workspace:packages/mono"
},
"dependencies": [],
"integrity": null,
"man_dir": "",
"origin": "npm",
"bin": null,
"scripts": {}
}
],
"workspace_paths": {
"11592711315645265694": "packages/bar",
"5128161233225832376": "packages/mono"
},
"workspace_versions": {
"11592711315645265694": "1.0.0"
}
}"
`;
exports[`dependency on workspace without version in package.json: version: 1 1`] = `
"{
"format": "v3",
@@ -1674,327 +1920,12 @@ exports[`dependency on workspace without version in package.json: version: *+bui
}"
`;
exports[`dependency on workspace without version in package.json: version: latest 1`] = `
"{
"format": "v3",
"meta_hash": "0000000000000000000000000000000000000000000000000000000000000000",
"package_index": {
"no-deps": [
2,
3
],
"foo": 0,
"bar": 1
},
"trees": [
{
"id": 0,
"path": "node_modules",
"depth": 0,
"dependencies": {
"bar": {
"id": 0,
"package_id": 1
},
"no-deps": {
"id": 1,
"package_id": 2
}
}
},
{
"id": 1,
"path": "node_modules/bar/node_modules",
"depth": 1,
"dependencies": {
"no-deps": {
"id": 2,
"package_id": 3
}
}
}
],
"dependencies": [
{
"name": "bar",
"literal": "",
"workspace": "packages/bar",
"package_id": 1,
"behavior": {
"workspace": true
},
"id": 0
},
{
"name": "no-deps",
"literal": "",
"workspace": "packages/mono",
"package_id": 2,
"behavior": {
"workspace": true
},
"id": 1
},
{
"name": "no-deps",
"literal": "latest",
"dist_tag": {
"name": "no-deps",
"tag": "no-deps"
},
"package_id": 3,
"behavior": {
"prod": true
},
"id": 2
}
],
"packages": [
{
"id": 0,
"name": "foo",
"name_hash": "14841791273925386894",
"resolution": {
"tag": "root",
"value": "",
"resolved": ""
},
"dependencies": [
0,
1
],
"integrity": null,
"man_dir": "",
"origin": "local",
"bin": null,
"scripts": {}
},
{
"id": 1,
"name": "bar",
"name_hash": "11592711315645265694",
"resolution": {
"tag": "workspace",
"value": "workspace:packages/bar",
"resolved": "workspace:packages/bar"
},
"dependencies": [
2
],
"integrity": null,
"man_dir": "",
"origin": "npm",
"bin": null,
"scripts": {}
},
{
"id": 2,
"name": "no-deps",
"name_hash": "5128161233225832376",
"resolution": {
"tag": "workspace",
"value": "workspace:packages/mono",
"resolved": "workspace:packages/mono"
},
"dependencies": [],
"integrity": null,
"man_dir": "",
"origin": "npm",
"bin": null,
"scripts": {}
},
{
"id": 3,
"name": "no-deps",
"name_hash": "5128161233225832376",
"resolution": {
"tag": "npm",
"value": "2.0.0",
"resolved": "http://localhost:1234/no-deps/-/no-deps-2.0.0.tgz"
},
"dependencies": [],
"integrity": "sha512-W3duJKZPcMIG5rA1io5cSK/bhW9rWFz+jFxZsKS/3suK4qHDkQNxUTEXee9/hTaAoDCeHWQqogukWYKzfr6X4g==",
"man_dir": "",
"origin": "npm",
"bin": null,
"scripts": {}
}
],
"workspace_paths": {
"11592711315645265694": "packages/bar",
"5128161233225832376": "packages/mono"
},
"workspace_versions": {
"11592711315645265694": "1.0.0"
}
}"
`;
exports[`dependency on workspace without version in package.json: version: 1`] = `
"{
"format": "v3",
"meta_hash": "0000000000000000000000000000000000000000000000000000000000000000",
"package_index": {
"no-deps": [
2,
3
],
"foo": 0,
"bar": 1
},
"trees": [
{
"id": 0,
"path": "node_modules",
"depth": 0,
"dependencies": {
"bar": {
"id": 0,
"package_id": 1
},
"no-deps": {
"id": 1,
"package_id": 2
}
}
},
{
"id": 1,
"path": "node_modules/bar/node_modules",
"depth": 1,
"dependencies": {
"no-deps": {
"id": 2,
"package_id": 3
}
}
}
],
"dependencies": [
{
"name": "bar",
"literal": "",
"workspace": "packages/bar",
"package_id": 1,
"behavior": {
"workspace": true
},
"id": 0
},
{
"name": "no-deps",
"literal": "",
"workspace": "packages/mono",
"package_id": 2,
"behavior": {
"workspace": true
},
"id": 1
},
{
"name": "no-deps",
"literal": "",
"dist_tag": {
"name": "no-deps",
"tag": "no-deps"
},
"package_id": 3,
"behavior": {
"prod": true
},
"id": 2
}
],
"packages": [
{
"id": 0,
"name": "foo",
"name_hash": "14841791273925386894",
"resolution": {
"tag": "root",
"value": "",
"resolved": ""
},
"dependencies": [
0,
1
],
"integrity": null,
"man_dir": "",
"origin": "local",
"bin": null,
"scripts": {}
},
{
"id": 1,
"name": "bar",
"name_hash": "11592711315645265694",
"resolution": {
"tag": "workspace",
"value": "workspace:packages/bar",
"resolved": "workspace:packages/bar"
},
"dependencies": [
2
],
"integrity": null,
"man_dir": "",
"origin": "npm",
"bin": null,
"scripts": {}
},
{
"id": 2,
"name": "no-deps",
"name_hash": "5128161233225832376",
"resolution": {
"tag": "workspace",
"value": "workspace:packages/mono",
"resolved": "workspace:packages/mono"
},
"dependencies": [],
"integrity": null,
"man_dir": "",
"origin": "npm",
"bin": null,
"scripts": {}
},
{
"id": 3,
"name": "no-deps",
"name_hash": "5128161233225832376",
"resolution": {
"tag": "npm",
"value": "2.0.0",
"resolved": "http://localhost:1234/no-deps/-/no-deps-2.0.0.tgz"
},
"dependencies": [],
"integrity": "sha512-W3duJKZPcMIG5rA1io5cSK/bhW9rWFz+jFxZsKS/3suK4qHDkQNxUTEXee9/hTaAoDCeHWQqogukWYKzfr6X4g==",
"man_dir": "",
"origin": "npm",
"bin": null,
"scripts": {}
}
],
"workspace_paths": {
"11592711315645265694": "packages/bar",
"5128161233225832376": "packages/mono"
},
"workspace_versions": {
"11592711315645265694": "1.0.0"
}
}"
`;
exports[`dependency on same name as workspace and dist-tag: with version 1`] = `
"{
"format": "v3",
"meta_hash": "0000000000000000000000000000000000000000000000000000000000000000",
"package_index": {
"no-deps": [
2,
3
],
"no-deps": 2,
"foo": 0,
"bar": 1
},
@@ -2013,17 +1944,6 @@ exports[`dependency on same name as workspace and dist-tag: with version 1`] = `
"package_id": 2
}
}
},
{
"id": 1,
"path": "node_modules/bar/node_modules",
"depth": 1,
"dependencies": {
"no-deps": {
"id": 2,
"package_id": 3
}
}
}
],
"dependencies": [
@@ -2050,11 +1970,8 @@ exports[`dependency on same name as workspace and dist-tag: with version 1`] = `
{
"name": "no-deps",
"literal": "latest",
"dist_tag": {
"name": "no-deps",
"tag": "no-deps"
},
"package_id": 3,
"workspace": "packages/mono",
"package_id": 2,
"behavior": {
"prod": true
},
@@ -2114,22 +2031,6 @@ exports[`dependency on same name as workspace and dist-tag: with version 1`] = `
"origin": "npm",
"bin": null,
"scripts": {}
},
{
"id": 3,
"name": "no-deps",
"name_hash": "5128161233225832376",
"resolution": {
"tag": "npm",
"value": "2.0.0",
"resolved": "http://localhost:1234/no-deps/-/no-deps-2.0.0.tgz"
},
"dependencies": [],
"integrity": "sha512-W3duJKZPcMIG5rA1io5cSK/bhW9rWFz+jFxZsKS/3suK4qHDkQNxUTEXee9/hTaAoDCeHWQqogukWYKzfr6X4g==",
"man_dir": "",
"origin": "npm",
"bin": null,
"scripts": {}
}
],
"workspace_paths": {

View File

@@ -66,17 +66,10 @@ test("dependency on workspace without version in package.json", async () => {
"kjwoehcojrgjoj", // dist-tag does not exist, should choose local workspace
"*.1.*",
"*-pre",
"latest", // dist-tag exists, should choose local workspace
"", // empty string is treated as "latest" dist tag, should choose local workspace
];
const shouldNotWork: string[] = [
"1",
"1.*",
"1.1.*",
"1.1.0",
"*-pre+build",
"*+build",
"latest", // dist-tag exists, should choose package from npm
"",
];
const shouldNotWork: string[] = ["1", "1.*", "1.1.*", "1.1.0", "*-pre+build", "*+build"];
for (const version of shouldWork) {
writeFileSync(
@@ -213,7 +206,7 @@ test("dependency on same name as workspace and dist-tag", async () => {
expect(out.replace(/\s*\[[0-9\.]+m?s\]\s*$/, "").split(/\r?\n/)).toEqual([
expect.stringContaining("bun install v1."),
"",
"3 packages installed",
"2 packages installed",
]);
});