Skip to content
64 changes: 61 additions & 3 deletions src/install/PackageManager/PackageManagerEnqueue.rs
Original file line number Diff line number Diff line change
Expand Up @@ -687,13 +687,15 @@ pub fn enqueue_dependency_with_main_and_success_fn(
}
}

// allow overriding all dependencies unless the dependency is coming directly from an alias, "npm:<this dep>" or
// if it's a workspaceOnly dependency
// allow overriding all dependencies unless the dependency is coming directly from an alias, "npm:<this dep>",
// is a workspaceOnly dependency, or links a workspace member of this install
Comment thread
robobun marked this conversation as resolved.
Resolved
if !dependency.behavior.is_workspace()
&& (dependency.version.tag != dependency::version::Tag::Npm
|| !dependency.version.npm().is_alias)
{
if let Some(new) = this.lockfile.overrides.get(name_hash) {
if let Some(new) = this.lockfile.overrides.get(name_hash)
&& !links_workspace_member(this, &dependency.version, name, name_hash)
{
bun_output::scoped_log!(
PackageManager,
"override: {} -> {}",
Expand Down Expand Up @@ -1908,6 +1910,62 @@ fn enqueue_local_tarball(
unsafe { &raw mut (*task).threadpool_task }
}

/// Would this edge, absent overrides, resolve to a workspace member of the
/// current install? Overrides skip such edges so they never displace a member
/// link. Mirrors the linking rules: `Package::parse` rewrites satisfying npm
/// ranges on versioned members to workspace links, and
/// `get_or_put_resolved_package` links wildcard ranges through the root's
/// workspace edges (`'resolve_from_workspace`). A `catalog:` edge links
/// through whatever version the catalog materializes to. Ranges that do not
/// link, and names whose member is missing from this run, stay overridable.
Comment thread
robobun marked this conversation as resolved.
Resolved
fn links_workspace_member(
this: &PackageManager,
version: &dependency::Version,
name: SemverString,
name_hash: PackageNameHash,
) -> bool {
let catalog_version;
let version = if version.tag == dependency::version::Tag::Catalog {
match this
.lockfile
.catalogs
.get(&this.lockfile, *version.catalog(), name)
{
Some(catalog_dep) => {
catalog_version = catalog_dep.version;
&catalog_version
}
None => return false,
}
} else {
version
};

match version.tag {
dependency::version::Tag::Workspace => this.lockfile.is_workspace_member_name(name_hash),
dependency::version::Tag::Npm => {
let npm = version.npm();
if npm.is_alias
|| !this.options.link_workspace_packages
|| !this.lockfile.is_workspace_member_name(name_hash)
{
return false;
}
if npm.version.is_star() {
return true;
}
let buf = this.lockfile.buffers.string_bytes.as_slice();
this.lockfile
.workspace_versions
.get(&name_hash)
.is_some_and(|workspace_version| {
npm.version.satisfies(*workspace_version, buf, buf)
})
}
_ => false,
}
}

fn update_name_and_name_hash_from_version_replacement(
lockfile: &Lockfile::Lockfile,
original_name: SemverString,
Expand Down
17 changes: 17 additions & 0 deletions src/install/lockfile.rs
Original file line number Diff line number Diff line change
Expand Up @@ -850,6 +850,23 @@ impl Lockfile {
self.packages.items_dependencies()[0].contains(id)
}

/// Is this name a workspace member of the current install, i.e. does the
/// root package carry an injected workspace edge for it? Unlike
/// `workspace_paths` (which a `workspace:` dependency with no matching
/// member also registers in) and `package_index` (which can still hold a
/// member a loaded lockfile resolved before the member directory was
/// removed), the root's workspace edges are rebuilt from package.json on
/// every install.
Comment thread
robobun marked this conversation as resolved.
Resolved
pub(crate) fn is_workspace_member_name(&self, name_hash: PackageNameHash) -> bool {
let Some(root) = self.root_package() else {
return false;
};
root.dependencies
.get(self.buffers.dependencies.as_slice())
.iter()
.any(|dep| dep.behavior.is_workspace() && dep.name_hash == name_hash)
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Resolved

/// Is this a direct dependency of the workspace the install is taking place in?
pub(crate) fn is_root_dependency(
&self,
Expand Down
5 changes: 4 additions & 1 deletion src/install/lockfile/bun.lock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3081,7 +3081,10 @@ fn resolve_peer_dep_version_based(
// already reflects the overridden resolution, so overridden edges keep
// the path walk. The exemptions mirror
// `enqueue_dependency_with_main_and_success_fn`: `npm:` aliases and
// workspace-only edges are never overridden.
// workspace-only edges are never overridden. The fresh resolver's
// member-link exemption needs no clause here: a workspace-tagged edge
// falls through to `None` either way (`satisfies_dependency_version`
// and the kind fallback below never match the Workspace tag).
Comment thread
robobun marked this conversation as resolved.
Resolved
let overridable = !dep.behavior.is_workspace()
&& (dep.version.tag != DependencyVersionTag::Npm || !dep.version.npm().is_alias);
Comment thread
robobun marked this conversation as resolved.
Resolved
if overridable && overrides.get(name_hash).is_some() {
Expand Down
Loading
Loading