aboutsummaryrefslogtreecommitdiffhomepage
path: root/crates/mozart/src/packagist.rs
diff options
context:
space:
mode:
authornsfisis <nsfisis@gmail.com>2026-02-21 14:19:42 +0900
committernsfisis <nsfisis@gmail.com>2026-02-21 14:19:42 +0900
commit2d46dc9091c4fa1b68361425c561dad773a343b4 (patch)
treee924acfb4ccb62a86136e78e8b27c1a8b862e90a /crates/mozart/src/packagist.rs
parent294bd3dd425a374eda13a52b925a2cd0c4db7f0a (diff)
downloadphp-mozart-2d46dc9091c4fa1b68361425c561dad773a343b4.tar.gz
php-mozart-2d46dc9091c4fa1b68361425c561dad773a343b4.tar.zst
php-mozart-2d46dc9091c4fa1b68361425c561dad773a343b4.zip
feat(resolver): add inline and branch alias support
Inline aliases ("1.0.x-dev as 1.0.0") now use the right side for constraint matching via parse_for_constraint(), while parse() keeps the left side for version identity. Branch aliases from extra.branch-alias metadata create synthetic dev-stability entries in the resolver, allowing constraints like ^2.0 to match dev-master aliased to 2.x-dev. Real releases take precedence via entry().or_insert(). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Diffstat (limited to 'crates/mozart/src/packagist.rs')
-rw-r--r--crates/mozart/src/packagist.rs130
1 files changed, 130 insertions, 0 deletions
diff --git a/crates/mozart/src/packagist.rs b/crates/mozart/src/packagist.rs
index b589a77..a92eb63 100644
--- a/crates/mozart/src/packagist.rs
+++ b/crates/mozart/src/packagist.rs
@@ -69,6 +69,41 @@ pub struct PackagistVersion {
pub notification_url: Option<String>,
}
+impl PackagistVersion {
+ /// Extract the `extra.branch-alias` map from this version's metadata.
+ ///
+ /// Composer packages can declare branch aliases in `extra.branch-alias`:
+ /// ```json
+ /// {
+ /// "extra": {
+ /// "branch-alias": {
+ /// "dev-master": "2.x-dev"
+ /// }
+ /// }
+ /// }
+ /// ```
+ ///
+ /// Returns a map from branch name (e.g. `"dev-master"`) to alias target
+ /// (e.g. `"2.x-dev"`). Returns an empty map when no aliases are declared.
+ pub fn branch_aliases(&self) -> BTreeMap<String, String> {
+ let Some(extra) = &self.extra else {
+ return BTreeMap::new();
+ };
+
+ let Some(branch_alias) = extra.get("branch-alias") else {
+ return BTreeMap::new();
+ };
+
+ let Some(map) = branch_alias.as_object() else {
+ return BTreeMap::new();
+ };
+
+ map.iter()
+ .filter_map(|(k, v)| v.as_str().map(|s| (k.clone(), s.to_string())))
+ .collect()
+ }
+}
+
/// Parse a Packagist p2 API JSON response.
///
/// The response format is: `{"packages": {"vendor/package": [...]}}`.
@@ -179,4 +214,99 @@ mod tests {
assert_eq!(versions[0].version, "dev-master");
assert_eq!(versions[1].version, "1.0.0");
}
+
+ // ──────────── branch_aliases() tests ────────────
+
+ #[test]
+ fn test_branch_aliases_present() {
+ let json = r#"{
+ "packages": {
+ "test/pkg": [
+ {
+ "version": "dev-master",
+ "version_normalized": "dev-master",
+ "require": {},
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.x-dev"
+ }
+ }
+ }
+ ]
+ }
+ }"#;
+
+ let versions = parse_p2_response(json, "test/pkg").unwrap();
+ let aliases = versions[0].branch_aliases();
+ assert_eq!(aliases.len(), 1);
+ assert_eq!(aliases.get("dev-master").unwrap(), "2.x-dev");
+ }
+
+ #[test]
+ fn test_branch_aliases_multiple() {
+ let json = r#"{
+ "packages": {
+ "test/pkg": [
+ {
+ "version": "dev-master",
+ "version_normalized": "dev-master",
+ "require": {},
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.x-dev",
+ "dev-1.x": "1.5.x-dev"
+ }
+ }
+ }
+ ]
+ }
+ }"#;
+
+ let versions = parse_p2_response(json, "test/pkg").unwrap();
+ let aliases = versions[0].branch_aliases();
+ assert_eq!(aliases.len(), 2);
+ assert_eq!(aliases.get("dev-master").unwrap(), "2.x-dev");
+ assert_eq!(aliases.get("dev-1.x").unwrap(), "1.5.x-dev");
+ }
+
+ #[test]
+ fn test_branch_aliases_no_extra() {
+ let json = r#"{
+ "packages": {
+ "test/pkg": [
+ {
+ "version": "dev-master",
+ "version_normalized": "dev-master",
+ "require": {}
+ }
+ ]
+ }
+ }"#;
+
+ let versions = parse_p2_response(json, "test/pkg").unwrap();
+ let aliases = versions[0].branch_aliases();
+ assert!(aliases.is_empty());
+ }
+
+ #[test]
+ fn test_branch_aliases_extra_without_branch_alias_key() {
+ let json = r#"{
+ "packages": {
+ "test/pkg": [
+ {
+ "version": "dev-master",
+ "version_normalized": "dev-master",
+ "require": {},
+ "extra": {
+ "installer-name": "my-plugin"
+ }
+ }
+ ]
+ }
+ }"#;
+
+ let versions = parse_p2_response(json, "test/pkg").unwrap();
+ let aliases = versions[0].branch_aliases();
+ assert!(aliases.is_empty());
+ }
}