From 43e06bb2aefab864939f4a49c96108ca028bc41d Mon Sep 17 00:00:00 2001 From: kimulaco <11986753+kimulaco@users.noreply.github.com> Date: Sun, 24 May 2026 03:40:25 +0900 Subject: [PATCH] fix(pacquet): accept string `libc` in `PackageMetadata` (#11880) --- .../crates/lockfile/src/package_metadata.rs | 35 ++++++++++++++- .../lockfile/src/package_metadata/tests.rs | 44 +++++++++++++++++++ 2 files changed, 77 insertions(+), 2 deletions(-) create mode 100644 pacquet/crates/lockfile/src/package_metadata/tests.rs diff --git a/pacquet/crates/lockfile/src/package_metadata.rs b/pacquet/crates/lockfile/src/package_metadata.rs index 85228aa906..318cbcf49c 100644 --- a/pacquet/crates/lockfile/src/package_metadata.rs +++ b/pacquet/crates/lockfile/src/package_metadata.rs @@ -1,5 +1,5 @@ use crate::LockfileResolution; -use serde::{Deserialize, Serialize}; +use serde::{Deserialize, Deserializer, Serialize}; use std::collections::HashMap; /// Metadata for one resolved package version, as stored in the v9 @@ -19,7 +19,11 @@ pub struct PackageMetadata { pub cpu: Option>, #[serde(skip_serializing_if = "Option::is_none")] pub os: Option>, - #[serde(skip_serializing_if = "Option::is_none")] + #[serde( + default, + skip_serializing_if = "Option::is_none", + deserialize_with = "deserialize_string_or_vec" + )] pub libc: Option>, #[serde(skip_serializing_if = "Option::is_none")] pub deprecated: Option, @@ -36,7 +40,34 @@ pub struct PackageMetadata { pub peer_dependencies_meta: Option>, } +// Some packages declare `libc` as a plain string in `package.json`; pnpm writes +// that string as-is into the lockfile. Accepts both string and array forms, +// normalizing to `Vec`. +fn deserialize_string_or_vec<'de, Value, Deser>( + deserializer: Deser, +) -> Result>, Deser::Error> +where + Value: Deserialize<'de>, + Deser: Deserializer<'de>, +{ + #[derive(Deserialize)] + #[serde(untagged)] + enum StringOrVec { + String(Value), + Vec(Vec), + } + + let opt = Option::>::deserialize(deserializer)?; + Ok(opt.map(|value| match value { + StringOrVec::String(item) => vec![item], + StringOrVec::Vec(items) => items, + })) +} + #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub struct PeerDependencyMeta { pub optional: bool, } + +#[cfg(test)] +mod tests; diff --git a/pacquet/crates/lockfile/src/package_metadata/tests.rs b/pacquet/crates/lockfile/src/package_metadata/tests.rs new file mode 100644 index 0000000000..4e9bb4de70 --- /dev/null +++ b/pacquet/crates/lockfile/src/package_metadata/tests.rs @@ -0,0 +1,44 @@ +use super::PackageMetadata; +use crate::serialize_yaml; +use text_block_macros::text_block; + +fn make_metadata(libc_yaml: &str) -> String { + let base = text_block! { + "resolution:" + " integrity: sha512-abc123" + " tarball: https://registry.npmjs.org/foo/-/foo-1.0.0.tgz" + "cpu: [arm64]" + "os: [linux]" + }; + format!("{base}\n{libc_yaml}") +} + +#[test] +fn libc_as_string() { + let yaml = make_metadata("libc: glibc\n"); + let metadata: PackageMetadata = serde_saphyr::from_str(&yaml).unwrap(); + assert_eq!(metadata.libc, Some(vec!["glibc".to_string()])); +} + +#[test] +fn libc_as_array() { + let yaml = make_metadata("libc: [glibc]\n"); + let metadata: PackageMetadata = serde_saphyr::from_str(&yaml).unwrap(); + assert_eq!(metadata.libc, Some(vec!["glibc".to_string()])); +} + +#[test] +fn libc_absent() { + let yaml = make_metadata(""); + let metadata: PackageMetadata = serde_saphyr::from_str(&yaml).unwrap(); + assert_eq!(metadata.libc, None); +} + +#[test] +fn libc_string_roundtrip() { + let yaml = make_metadata("libc: glibc\n"); + let metadata: PackageMetadata = serde_saphyr::from_str(&yaml).unwrap(); + let serialized = serialize_yaml::to_string(&metadata).unwrap(); + let reparsed: PackageMetadata = serde_saphyr::from_str(&serialized).unwrap(); + assert_eq!(metadata.libc, reparsed.libc); +}