From 8a87adf120d5057b06d0474b293fab079e1ce967 Mon Sep 17 00:00:00 2001 From: nsfisis Date: Fri, 1 May 2026 22:02:32 +0900 Subject: fix(install): preserve LockedPackage extra_fields in installed.json Composer's InstalledFilesystemRepository::write() dumps the full package via ArrayDumper, so flags like `abandoned` and `default-branch` (which Mozart parks in LockedPackage::extra_fields) should round-trip from composer.lock into vendor/composer/installed.json. locked_to_installed_entry was zeroing the destination's extra_fields, silently stripping these flags every time installed.json got rewritten. Carry the extra_fields map across verbatim. The install-forces-reinstall-if-abandon-changes installer fixture is already exit-0 green at the harness layer; this aligns the actual end-state with Composer's EXPECT-INSTALLED so a future EXPECT-INSTALLED comparison won't re-flag this gap. Co-Authored-By: Claude Opus 4.7 (1M context) --- crates/mozart/src/commands/install.rs | 35 ++++++++++++++++++++++++++++++++++- crates/mozart/tests/installer.rs | 5 +---- 2 files changed, 35 insertions(+), 5 deletions(-) diff --git a/crates/mozart/src/commands/install.rs b/crates/mozart/src/commands/install.rs index b9ff1af..a10dd69 100644 --- a/crates/mozart/src/commands/install.rs +++ b/crates/mozart/src/commands/install.rs @@ -204,6 +204,11 @@ pub fn compute_operations<'a>( } /// Convert a LockedPackage to an InstalledPackageEntry. +/// +/// `LockedPackage::extra_fields` is forwarded verbatim so flags like +/// `abandoned` and `default-branch` survive the lock → installed.json round +/// trip, matching Composer's `InstalledFilesystemRepository::write()` (which +/// dumps the full package via `ArrayDumper`). pub fn locked_to_installed_entry( pkg: &lockfile::LockedPackage, _vendor_dir: &Path, @@ -227,7 +232,7 @@ pub fn locked_to_installed_entry( install_path: Some(install_path), autoload: pkg.autoload.clone(), aliases: vec![], - extra_fields: BTreeMap::new(), + extra_fields: pkg.extra_fields.clone(), } } @@ -1122,6 +1127,34 @@ mod tests { assert!(entry.dist.is_some()); } + #[test] + fn test_locked_to_installed_entry_propagates_extra_fields() { + // Composer's installed.json carries package flags like `abandoned` and + // `default-branch` that LockedPackage stores in extra_fields. Make sure + // they survive the conversion so we don't strip them on rewrite. + let dir = tempdir().unwrap(); + let vendor_dir = dir.path().join("vendor"); + + let mut pkg = make_locked_package("a/a", "1.0.0"); + pkg.extra_fields.insert( + "abandoned".to_string(), + serde_json::Value::String("replacement".to_string()), + ); + pkg.extra_fields + .insert("default-branch".to_string(), serde_json::Value::Bool(true)); + + let entry = locked_to_installed_entry(&pkg, &vendor_dir); + + assert_eq!( + entry.extra_fields.get("abandoned"), + Some(&serde_json::Value::String("replacement".to_string())) + ); + assert_eq!( + entry.extra_fields.get("default-branch"), + Some(&serde_json::Value::Bool(true)) + ); + } + // ----------------------------------------------------------------------- // installed.json generation tests // ----------------------------------------------------------------------- diff --git a/crates/mozart/tests/installer.rs b/crates/mozart/tests/installer.rs index 314771c..7af07b5 100644 --- a/crates/mozart/tests/installer.rs +++ b/crates/mozart/tests/installer.rs @@ -242,10 +242,7 @@ installer_fixture!( install_dev_using_dist, ignore = "mozart binary cannot yet run this fixture" ); -installer_fixture!( - install_forces_reinstall_if_abandon_changes, - ignore = "mozart binary cannot yet run this fixture" -); +installer_fixture!(install_forces_reinstall_if_abandon_changes); installer_fixture!( install_from_incomplete_lock, ignore = "mozart binary cannot yet run this fixture" -- cgit v1.3.1