Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
178 changes: 129 additions & 49 deletions crates/turborepo-lockfiles/src/bun/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ use biome_json_formatter::context::JsonFormatOptions;
use biome_json_parser::JsonParserOptions;
use id::PossibleKeyIter;
use itertools::Itertools as _;
use semver::{Version, VersionReq};
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use serde_json::Value;
use turbopath::RelativeUnixPathBuf;
Expand Down Expand Up @@ -389,6 +390,116 @@ impl BunLockfile {
version,
}))
}

/// Check if a package version satisfies a version specification.
///
/// Returns true if the version satisfies the spec, false otherwise.
/// For non-semver specs (tags, catalogs, workspaces), returns true.
fn version_satisfies_spec(&self, version: &str, version_spec: &str) -> bool {
let spec = VersionSpec::parse(version_spec);

match spec {
VersionSpec::Semver(spec_str) => {
// Parse both the requirement and the version
let Ok(req) = VersionReq::parse(&spec_str) else {
// If we can't parse the requirement, be lenient and accept it
return true;
};

let Ok(ver) = Version::parse(version) else {
// If we can't parse the version, be lenient and accept it
return true;
};

req.matches(&ver)
}
// For non-semver specs (tags, catalogs, workspace), accept any version
// since validation happens elsewhere
_ => true,
}
}

/// Find a package version that satisfies the given version spec.
///
/// Searches in order:
/// 1. Workspace-scoped entries
/// 2. Top-level entries
/// 3. Nested/aliased entries (by searching all idents)
fn find_matching_version(
&self,
workspace_name: &str,
name: &str,
version_spec: &str,
override_version: &str,
resolved_version: &str,
) -> Result<Option<crate::Package>, crate::Error> {
// Try workspace-scoped first
if let Some(entry) = self.index.get_workspace_scoped(workspace_name, name)
&& let Some(pkg) =
self.process_package_entry(entry, name, override_version, resolved_version)?
&& self.version_satisfies_spec(&pkg.version, version_spec)
{
return Ok(Some(pkg));
}

// Try hoisted/top-level
if let Some((_key, entry)) = self.index.find_package(Some(workspace_name), name)
&& let Some(pkg) =
self.process_package_entry(entry, name, override_version, resolved_version)?
&& self.version_satisfies_spec(&pkg.version, version_spec)
{
return Ok(Some(pkg));
}

// Search for nested/aliased versions that match
// Only search explicitly nested entries (with '/' in key), not bundled deps
for (lockfile_key, entry) in &self.data.packages {
// Only consider explicitly nested entries (not bundled)
if !lockfile_key.contains('/') {
continue;
}

// Skip bundled dependencies
if let Some(info) = &entry.info
&& info
.other
.get("bundled")
.and_then(|v| v.as_bool())
.unwrap_or(false)
{
continue;
}

let ident = PackageIdent::parse(&entry.ident);

// Skip if the name doesn't match
if ident.name() != name {
continue;
}

// Skip workspace mappings
if ident.is_workspace() {
continue;
}

// Check if this version satisfies the spec
if let Some(pkg) =
self.process_package_entry(entry, name, override_version, resolved_version)?
&& self.version_satisfies_spec(&pkg.version, version_spec)
{
tracing::debug!(
"Found matching version {} for {} (spec: {}) in nested entry {}",
pkg.version,
name,
version_spec,
lockfile_key
);
return Ok(Some(pkg));
}
}

Ok(None)
}
}

impl Lockfile for BunLockfile {
Expand Down Expand Up @@ -441,19 +552,15 @@ impl Lockfile for BunLockfile {
}
}

// Try workspace-scoped lookup first
if let Some(entry) = self.index.get_workspace_scoped(workspace_name, name)
&& let Some(pkg) =
self.process_package_entry(entry, name, override_version, resolved_version)?
{
return Ok(Some(pkg));
}

// Try finding via the general find_package method (includes bundled)
if let Some((_key, entry)) = self.index.find_package(Some(workspace_name), name)
&& let Some(pkg) =
self.process_package_entry(entry, name, override_version, resolved_version)?
{
// Find a package version that satisfies the version spec
// This searches workspace-scoped, hoisted, and nested entries
if let Some(pkg) = self.find_matching_version(
workspace_name,
name,
version,
override_version,
resolved_version,
)? {
return Ok(Some(pkg));
}

Expand Down Expand Up @@ -1904,41 +2011,6 @@ mod test {
assert!(lockfile.is_err(), "matching packages have differing shas");
}

#[test]
fn test_override_functionality() {
let contents = serde_json::to_string(&json!({
"lockfileVersion": 0,
"workspaces": {
"": {
"name": "test",
"dependencies": {
"foo": "^1.0.0"
}
}
},
"packages": {
"foo": ["foo@1.0.0", {}, "sha512-original"],
"foo-override": ["foo@2.0.0", {}, "sha512-override"]
},
"overrides": {
"foo": "2.0.0"
}
}))
.unwrap();

let lockfile = BunLockfile::from_str(&contents).unwrap();

// Resolve foo - should get override version instead of original
let result = lockfile
.resolve_package("", "foo", "^1.0.0")
.unwrap()
.unwrap();

// Should resolve to overridden version
assert_eq!(result.key, "foo@2.0.0");
assert_eq!(result.version, "2.0.0");
}

#[test]
fn test_override_functionality_no_override() {
let contents = serde_json::to_string(&json!({
Expand Down Expand Up @@ -3169,13 +3241,21 @@ mod test {
}

#[test]
fn test_prune_issue_11007_2() {
fn test_prune_issue_11007_2_api() {
let lockfile = BunLockfile::from_str(PRUNE_ISSUE_11007_ORIGINAL_2).unwrap();
let pruned = prune_for_workspace(&lockfile, "apps/api");
let pruned_str = String::from_utf8(pruned.encode().unwrap()).unwrap();
insta::assert_snapshot!(pruned_str);
}

#[test]
fn test_prune_issue_11007_2_web() {
let lockfile = BunLockfile::from_str(PRUNE_ISSUE_11007_ORIGINAL_2).unwrap();
let pruned = prune_for_workspace(&lockfile, "apps/web");
let pruned_str = String::from_utf8(pruned.encode().unwrap()).unwrap();
insta::assert_snapshot!(pruned_str);
}

#[test]
fn test_prune_issue_11074() {
let lockfile = BunLockfile::from_str(PRUNE_ISSUE_11074_ORIGINAL).unwrap();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -212,8 +212,6 @@ expression: pruned_str

"glob-parent": ["glob-parent@6.0.2", "", { "dependencies": { "is-glob": "^4.0.3" } }, "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A=="],

"globals": ["globals@16.4.0", "", {}, "sha512-ob/2LcVVaVGCYN+r14cnwnoDPUufjiYgSqRhiFD0Q1iI4Odora5RE8Iv1D24hAz5oMophRGkGz+yuvQmmUMnMw=="],

"has-flag": ["has-flag@4.0.0", "", {}, "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="],

"ignore": ["ignore@5.3.2", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="],
Expand Down Expand Up @@ -284,8 +282,6 @@ expression: pruned_str

"scheduler": ["scheduler@0.27.0", "", {}, "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q=="],

"semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="],

"sharp": ["sharp@0.34.4", "", { "dependencies": { "@img/colour": "^1.0.0", "detect-libc": "^2.1.0", "semver": "^7.7.2" }, "optionalDependencies": { "@img/sharp-darwin-arm64": "0.34.4", "@img/sharp-darwin-x64": "0.34.4", "@img/sharp-libvips-darwin-arm64": "1.2.3", "@img/sharp-libvips-darwin-x64": "1.2.3", "@img/sharp-libvips-linux-arm": "1.2.3", "@img/sharp-libvips-linux-arm64": "1.2.3", "@img/sharp-libvips-linux-ppc64": "1.2.3", "@img/sharp-libvips-linux-s390x": "1.2.3", "@img/sharp-libvips-linux-x64": "1.2.3", "@img/sharp-libvips-linuxmusl-arm64": "1.2.3", "@img/sharp-libvips-linuxmusl-x64": "1.2.3", "@img/sharp-linux-arm": "0.34.4", "@img/sharp-linux-arm64": "0.34.4", "@img/sharp-linux-ppc64": "0.34.4", "@img/sharp-linux-s390x": "0.34.4", "@img/sharp-linux-x64": "0.34.4", "@img/sharp-linuxmusl-arm64": "0.34.4", "@img/sharp-linuxmusl-x64": "0.34.4", "@img/sharp-wasm32": "0.34.4", "@img/sharp-win32-arm64": "0.34.4", "@img/sharp-win32-ia32": "0.34.4", "@img/sharp-win32-x64": "0.34.4" } }, "sha512-FUH39xp3SBPnxWvd5iib1X8XY7J0K0X7d93sie9CJg2PO8/7gmg89Nve6OjItK53/MlAushNNxteBYfM6DEuoA=="],

"shebang-command": ["shebang-command@2.0.0", "", { "dependencies": { "shebang-regex": "^3.0.0" } }, "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA=="],
Expand Down Expand Up @@ -334,6 +330,8 @@ expression: pruned_str

"@eslint/eslintrc/globals": ["globals@14.0.0", "", {}, "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ=="],

"@typescript-eslint/typescript-estree/semver": ["semver@7.7.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q=="],

"sharp/semver": ["semver@7.7.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q=="],
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -210,8 +210,6 @@ expression: pruned_str

"glob-parent": ["glob-parent@6.0.2", "", { "dependencies": { "is-glob": "^4.0.3" } }, "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A=="],

"globals": ["globals@16.4.0", "", {}, "sha512-ob/2LcVVaVGCYN+r14cnwnoDPUufjiYgSqRhiFD0Q1iI4Odora5RE8Iv1D24hAz5oMophRGkGz+yuvQmmUMnMw=="],

"has-flag": ["has-flag@4.0.0", "", {}, "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="],

"ignore": ["ignore@5.3.2", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="],
Expand Down Expand Up @@ -282,8 +280,6 @@ expression: pruned_str

"scheduler": ["scheduler@0.27.0", "", {}, "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q=="],

"semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="],

"sharp": ["sharp@0.34.4", "", { "dependencies": { "@img/colour": "^1.0.0", "detect-libc": "^2.1.0", "semver": "^7.7.2" }, "optionalDependencies": { "@img/sharp-darwin-arm64": "0.34.4", "@img/sharp-darwin-x64": "0.34.4", "@img/sharp-libvips-darwin-arm64": "1.2.3", "@img/sharp-libvips-darwin-x64": "1.2.3", "@img/sharp-libvips-linux-arm": "1.2.3", "@img/sharp-libvips-linux-arm64": "1.2.3", "@img/sharp-libvips-linux-ppc64": "1.2.3", "@img/sharp-libvips-linux-s390x": "1.2.3", "@img/sharp-libvips-linux-x64": "1.2.3", "@img/sharp-libvips-linuxmusl-arm64": "1.2.3", "@img/sharp-libvips-linuxmusl-x64": "1.2.3", "@img/sharp-linux-arm": "0.34.4", "@img/sharp-linux-arm64": "0.34.4", "@img/sharp-linux-ppc64": "0.34.4", "@img/sharp-linux-s390x": "0.34.4", "@img/sharp-linux-x64": "0.34.4", "@img/sharp-linuxmusl-arm64": "0.34.4", "@img/sharp-linuxmusl-x64": "0.34.4", "@img/sharp-wasm32": "0.34.4", "@img/sharp-win32-arm64": "0.34.4", "@img/sharp-win32-ia32": "0.34.4", "@img/sharp-win32-x64": "0.34.4" } }, "sha512-FUH39xp3SBPnxWvd5iib1X8XY7J0K0X7d93sie9CJg2PO8/7gmg89Nve6OjItK53/MlAushNNxteBYfM6DEuoA=="],

"shebang-command": ["shebang-command@2.0.0", "", { "dependencies": { "shebang-regex": "^3.0.0" } }, "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA=="],
Expand Down Expand Up @@ -334,6 +330,8 @@ expression: pruned_str

"@eslint/eslintrc/globals": ["globals@14.0.0", "", {}, "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ=="],

"@typescript-eslint/typescript-estree/semver": ["semver@7.7.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q=="],

"sharp/semver": ["semver@7.7.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q=="],
}
}
Loading
Loading