Skip to content

Commit ffb7560

Browse files
authored
fix(bun): deserialize correctly and use optionalPeers (#10219)
### Description Resolves #9058 and #7456 (again) When I tested my previous PR (#10175) on another repo, I discovered an issue with a missing dep in the pruned lockfile To reproduce this in the `with-yarn` example repo: `cd apps/web && bun install dd-trace` and `cd apps/docs && bun install @opentelemtry/api` Then run prune for `web` and try `bun install --frozen-lockfile` and you'll get this: ``` error: Failed to resolve peer dependency '@opentelemetry/api' for package '@opentelemetry/core' at bun.lock:1:16974 InvalidPackageInfo: failed to parse lockfile: 'bun.lock' ``` ### Issue 1 -- Deserialization casing The underlying error is rather straightforward -- it was deserializing most dependencies (`peerDependencies`, `devDependencies`, `optionalDependencies`) to `other` 😄 ![image](https://github.com/user-attachments/assets/0ca602de-c88a-4715-8db5-2f4cb620f95b) This is because it just needed `#[serde(rename_all = "camelCase")]` since the rust names are snake case. This is why `dependencies` happened to work -- it's the same in snake and camel case. By virtue of being in `other`, all non-`dependencies` packages were effectively ignored during the pruning process, and don't always end up in the resulting lockfile. ### Issue 2 -- optionalPeers After fixing that, I also stumbled upon another issue: ``` WARNING Unable to calculate transitive closures: No lockfile entry found for 'next/@playwright/test' ``` This is because we weren't accounting for the `optionalPeers` property. `next` doesn't actually require you have `@playwright/test`, it's an optional peer. I fixed this as well.
1 parent f063969 commit ffb7560

File tree

3 files changed

+37
-3
lines changed

3 files changed

+37
-3
lines changed

crates/turborepo-lockfiles/src/bun/de.rs

+11-1
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,16 @@ mod test {
153153
dependencies: Some(("is-number".into(), "^6.0.0".into()))
154154
.into_iter()
155155
.collect(),
156+
dev_dependencies: Some(("is-bigint".into(), "1.1.0".into()))
157+
.into_iter()
158+
.collect(),
159+
peer_dependencies: Some(("is-even".into(), "1.0.0".into()))
160+
.into_iter()
161+
.collect(),
162+
optional_peers: Some("is-even".into()).into_iter().collect(),
163+
optional_dependencies: Some(("is-regexp".into(), "1.0.0".into()))
164+
.into_iter()
165+
.collect(),
156166
..Default::default()
157167
}),
158168
checksum: Some("sha".into()),
@@ -193,7 +203,7 @@ mod test {
193203
);
194204
#[test_case(json!({"name": "bun-test", "devDependencies": {"turbo": "^2.3.3"}}), basic_workspace() ; "basic")]
195205
#[test_case(json!({"name": "docs", "version": "0.1.0"}), workspace_with_version() ; "with version")]
196-
#[test_case(json!(["[email protected]", "", {"dependencies": {"is-number": "^6.0.0"}}, "sha"]), registry_pkg() ; "registry package")]
206+
#[test_case(json!(["[email protected]", "", {"dependencies": {"is-number": "^6.0.0"}, "devDependencies": {"is-bigint": "1.1.0"}, "peerDependencies": {"is-even": "1.0.0"}, "optionalDependencies": {"is-regexp": "1.0.0"}, "optionalPeers": ["is-even"]}, "sha"]), registry_pkg() ; "registry package")]
197207
#[test_case(json!(["docs", {"dependencies": {"is-odd": "3.0.1"}}]), workspace_pkg() ; "workspace package")]
198208
#[test_case(json!(["some-package@root:", {"bin": "bin", "binDir": "binDir"}]), root_pkg() ; "root package")]
199209
fn test_deserialization<T: for<'a> Deserialize<'a> + PartialEq + std::fmt::Debug>(

crates/turborepo-lockfiles/src/bun/mod.rs

+15-1
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@ struct PackageEntry {
8383
}
8484

8585
#[derive(Debug, Deserialize, Default, PartialEq, Clone, Serialize)]
86+
#[serde(rename_all = "camelCase")]
8687
struct PackageInfo {
8788
#[serde(default, skip_serializing_if = "Map::is_empty")]
8889
dependencies: Map<String, String>,
@@ -92,6 +93,8 @@ struct PackageInfo {
9293
optional_dependencies: Map<String, String>,
9394
#[serde(default, skip_serializing_if = "Map::is_empty")]
9495
peer_dependencies: Map<String, String>,
96+
#[serde(default, skip_serializing_if = "HashSet::is_empty")]
97+
optional_peers: HashSet<String>,
9598
// We do not care about the rest here
9699
// the values here should be generic
97100
#[serde(flatten)]
@@ -163,9 +166,20 @@ impl Lockfile for BunLockfile {
163166
.ok_or_else(|| crate::Error::MissingPackage(key.into()))?;
164167

165168
let mut deps = HashMap::new();
166-
for (dependency, version) in entry.info.iter().flat_map(|info| info.all_dependencies()) {
169+
170+
let Some(info) = &entry.info else {
171+
return Ok(Some(deps));
172+
};
173+
174+
let optional_peers = &info.optional_peers;
175+
for (dependency, version) in info.all_dependencies() {
167176
let parent_key = format!("{entry_key}/{dependency}");
168177
let Some((dep_key, _)) = self.package_entry(&parent_key) else {
178+
// This is an optional peer dependency
179+
if optional_peers.contains(&dependency.to_string()) {
180+
continue;
181+
}
182+
169183
return Err(crate::Error::MissingPackage(dependency.to_string()));
170184
};
171185
deps.insert(dep_key.to_string(), version.to_string());

crates/turborepo-lockfiles/src/bun/ser.rs

+11-1
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,16 @@ mod test {
103103
dependencies: Some(("is-number".into(), "^6.0.0".into()))
104104
.into_iter()
105105
.collect(),
106+
dev_dependencies: Some(("is-bigint".into(), "1.1.0".into()))
107+
.into_iter()
108+
.collect(),
109+
peer_dependencies: Some(("is-even".into(), "1.0.0".into()))
110+
.into_iter()
111+
.collect(),
112+
optional_peers: Some("is-even".into()).into_iter().collect(),
113+
optional_dependencies: Some(("is-regexp".into(), "1.0.0".into()))
114+
.into_iter()
115+
.collect(),
106116
..Default::default()
107117
}),
108118
checksum: Some("sha".into()),
@@ -143,7 +153,7 @@ mod test {
143153
);
144154
#[test_case(json!({"name": "bun-test", "devDependencies": {"turbo": "^2.3.3"}}), basic_workspace() ; "basic")]
145155
#[test_case(json!({"name": "docs", "version": "0.1.0"}), workspace_with_version() ; "with version")]
146-
#[test_case(json!(["[email protected]", "", {"dependencies": {"is-number": "^6.0.0"}}, "sha"]), registry_pkg() ; "registry package")]
156+
#[test_case(json!(["[email protected]", "", {"dependencies": {"is-number": "^6.0.0"}, "devDependencies": {"is-bigint": "1.1.0"}, "peerDependencies": {"is-even": "1.0.0"}, "optionalDependencies": {"is-regexp": "1.0.0"}, "optionalPeers": ["is-even"]}, "sha"]), registry_pkg() ; "registry package")]
147157
#[test_case(json!(["docs", {"dependencies": {"is-odd": "3.0.1"}}]), workspace_pkg() ; "workspace package")]
148158
#[test_case(json!(["some-package@root:", {"bin": "bin", "binDir": "binDir"}]), root_pkg() ; "root package")]
149159
fn test_serialization<T: Serialize + PartialEq + std::fmt::Debug>(

0 commit comments

Comments
 (0)