From b3449600d266d2fd5d261d73c3d012ceeb5d40a0 Mon Sep 17 00:00:00 2001 From: Anthony Shew Date: Thu, 2 Oct 2025 10:21:18 -0700 Subject: [PATCH 1/2] fix(lockfiles): include bundled dependencies in Bun lockfile subgraphs When creating pruned lockfiles (subgraphs), the Bun lockfile parser was not including bundled dependencies that are stored with scoped package keys. This caused 'bun install' to fail with errors like: error: Failed to resolve prod dependency '@emnapi/core' for package '@tailwindcss/oxide-wasm32-wasi' Bundled dependencies in Bun lockfiles are stored with scoped keys (e.g., '@tailwindcss/oxide-wasm32-wasi/@emnapi/core') and have 'bundled': true in their metadata. When filtering packages for a subgraph, we now: 1. Collect packages matching the requested idents 2. For each included package, check for scoped dependencies (keys starting with '/') 3. Include those scoped dependencies if they have 'bundled': true This ensures bundled dependencies are preserved in pruned lockfiles while not including regular scoped overrides. Fixes the issue where pruned lockfiles would fail to install with Bun. --- crates/turborepo-lockfiles/src/bun/mod.rs | 116 +++++++++++++++++++++- 1 file changed, 115 insertions(+), 1 deletion(-) diff --git a/crates/turborepo-lockfiles/src/bun/mod.rs b/crates/turborepo-lockfiles/src/bun/mod.rs index b42672e3c7dbf..a462d36547824 100644 --- a/crates/turborepo-lockfiles/src/bun/mod.rs +++ b/crates/turborepo-lockfiles/src/bun/mod.rs @@ -607,7 +607,9 @@ impl BunLockfile { // Filter out packages that are not in the subgraph. Note that _multiple_ // entries can correspond to the same ident. let idents: HashSet<_> = packages.iter().collect(); - let new_packages: Map<_, _> = self + + // First, collect packages that match the idents + let mut new_packages: Map<_, _> = self .data .packages .iter() @@ -620,6 +622,24 @@ impl BunLockfile { }) .collect(); + // Then, for each included package, also include any scoped bundled dependencies + // Bundled dependencies are stored as "/" in the packages + // map and have "bundled": true in their metadata + let parent_keys: Vec<_> = new_packages.keys().cloned().collect(); + for parent_key in parent_keys { + let prefix = format!("{}/", parent_key); + for (key, entry) in self.data.packages.iter() { + if key.starts_with(&prefix) && !new_packages.contains_key(key) { + // Check if this is a bundled dependency + if let Some(info) = &entry.info { + if info.other.get("bundled") == Some(&Value::Bool(true)) { + new_packages.insert(key.clone(), entry.clone()); + } + } + } + } + } + let new_patched_dependencies = self .data .patched_dependencies @@ -3690,6 +3710,100 @@ mod test { assert_eq!(common_info.cpu, Negatable::None); } + #[test] + fn test_bundled_dependencies_included_in_subgraph() { + let contents = serde_json::to_string(&json!({ + "lockfileVersion": 1, + "workspaces": { + "": { + "name": "test-app", + "dependencies": { + "@tailwindcss/oxide-wasm32-wasi": "^4.1.13" + } + } + }, + "packages": { + "@tailwindcss/oxide-wasm32-wasi": [ + "@tailwindcss/oxide-wasm32-wasi@4.1.13", + "", + { + "dependencies": { + "@emnapi/core": "^1.4.5", + "@emnapi/runtime": "^1.4.5" + } + }, + "sha512-test" + ], + "@tailwindcss/oxide-wasm32-wasi/@emnapi/core": [ + "@emnapi/core@1.5.0", + "", + { + "dependencies": { + "tslib": "^2.4.0" + }, + "bundled": true + }, + "sha512-bundled-core" + ], + "@tailwindcss/oxide-wasm32-wasi/@emnapi/runtime": [ + "@emnapi/runtime@1.5.0", + "", + { + "dependencies": { + "tslib": "^2.4.0" + }, + "bundled": true + }, + "sha512-bundled-runtime" + ], + "@tailwindcss/oxide-wasm32-wasi/tslib": [ + "tslib@2.8.1", + "", + { + "bundled": true + }, + "sha512-bundled-tslib" + ] + } + })) + .unwrap(); + + let lockfile = BunLockfile::from_str(&contents).unwrap(); + + // Create subgraph including @tailwindcss/oxide-wasm32-wasi + let subgraph = lockfile + .subgraph(&[], &["@tailwindcss/oxide-wasm32-wasi@4.1.13".into()]) + .unwrap(); + let subgraph_data = subgraph.lockfile().unwrap(); + + // Verify the main package is included + assert!( + subgraph_data + .packages + .contains_key("@tailwindcss/oxide-wasm32-wasi") + ); + + // Verify bundled dependencies are included + assert!( + subgraph_data + .packages + .contains_key("@tailwindcss/oxide-wasm32-wasi/@emnapi/core") + ); + assert!( + subgraph_data + .packages + .contains_key("@tailwindcss/oxide-wasm32-wasi/@emnapi/runtime") + ); + assert!( + subgraph_data + .packages + .contains_key("@tailwindcss/oxide-wasm32-wasi/tslib") + ); + + // Verify the count is correct (1 main + 3 bundled) + assert_eq!(subgraph_data.packages.len(), 4); + } + #[test] fn test_complex_platform_constraints() { let contents = serde_json::to_string(&json!({ From e94860ca261a25646cc0523992e3d8a38ac887c9 Mon Sep 17 00:00:00 2001 From: Anthony Shew Date: Thu, 2 Oct 2025 10:31:28 -0700 Subject: [PATCH 2/2] fix: address clippy lints in bun lockfile code --- crates/turborepo-lockfiles/src/bun/mod.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/crates/turborepo-lockfiles/src/bun/mod.rs b/crates/turborepo-lockfiles/src/bun/mod.rs index a462d36547824..c97ed89e2e744 100644 --- a/crates/turborepo-lockfiles/src/bun/mod.rs +++ b/crates/turborepo-lockfiles/src/bun/mod.rs @@ -627,14 +627,14 @@ impl BunLockfile { // map and have "bundled": true in their metadata let parent_keys: Vec<_> = new_packages.keys().cloned().collect(); for parent_key in parent_keys { - let prefix = format!("{}/", parent_key); + let prefix = format!("{parent_key}/"); for (key, entry) in self.data.packages.iter() { if key.starts_with(&prefix) && !new_packages.contains_key(key) { // Check if this is a bundled dependency - if let Some(info) = &entry.info { - if info.other.get("bundled") == Some(&Value::Bool(true)) { - new_packages.insert(key.clone(), entry.clone()); - } + if let Some(info) = &entry.info + && info.other.get("bundled") == Some(&Value::Bool(true)) + { + new_packages.insert(key.clone(), entry.clone()); } } }