use {
crate::{
cli::SortBy,
dependency::{Dependency, UpdateUrl},
dependency_type::DependencyType,
group_selector::GroupSelector,
instance::Instance,
package_json::PackageJson,
packages::Packages,
specifier::Specifier,
},
itertools::Itertools,
log::warn,
serde::Deserialize,
serde_json::Value,
std::{
cell::RefCell,
cmp::Ordering,
collections::{BTreeMap, HashMap},
rc::Rc,
vec,
},
};
#[derive(Clone, Debug)]
pub enum VersionGroupVariant {
Banned,
HighestSemver,
Ignored,
LowestSemver,
Pinned,
SameRange,
SameMinor,
SnappedTo,
}
#[derive(Debug)]
pub struct VersionGroup {
pub dependencies: BTreeMap<String, Dependency>,
pub matches_cli_filter: bool,
pub pin_version: Option<Rc<Specifier>>,
pub selector: GroupSelector,
pub snap_to: Option<Vec<Rc<RefCell<PackageJson>>>>,
pub variant: VersionGroupVariant,
}
impl VersionGroup {
pub fn get_catch_all(all_dependency_types: &[DependencyType]) -> VersionGroup {
VersionGroup {
dependencies: BTreeMap::new(),
matches_cli_filter: false,
pin_version: None,
selector: GroupSelector::new(
&Packages::new(),
vec![],
vec![],
"Default Version Group".to_string(),
vec![],
vec![],
all_dependency_types,
),
snap_to: None,
variant: VersionGroupVariant::HighestSemver,
}
}
pub fn add_instance(&mut self, instance: Rc<Instance>) {
let dependency = self
.dependencies
.entry(instance.descriptor.internal_name.clone())
.or_insert_with(|| {
Dependency::new(
instance.descriptor.internal_name.clone(),
self.variant.clone(),
self.pin_version.clone(),
self.snap_to.clone(),
)
});
if instance.descriptor.name != dependency.internal_name {
dependency.has_alias = true;
}
if instance.descriptor.matches_cli_filter {
self.matches_cli_filter = true;
dependency.matches_cli_filter = true;
}
dependency.add_instance(Rc::clone(&instance));
}
pub fn from_config(group: &AnyVersionGroup, packages: &Packages, all_dependency_types: &[DependencyType]) -> VersionGroup {
let selector = GroupSelector::new(
packages,
group.dependencies.clone(),
group.dependency_types.clone(),
group.label.clone(),
group.packages.clone(),
group.specifier_types.clone(),
all_dependency_types,
);
if let Some(true) = group.is_banned {
return VersionGroup {
dependencies: BTreeMap::new(),
matches_cli_filter: false,
pin_version: None,
selector,
snap_to: None,
variant: VersionGroupVariant::Banned,
};
}
if let Some(true) = group.is_ignored {
return VersionGroup {
dependencies: BTreeMap::new(),
matches_cli_filter: false,
pin_version: None,
selector,
snap_to: None,
variant: VersionGroupVariant::Ignored,
};
}
if let Some(pin_version) = &group.pin_version {
return VersionGroup {
dependencies: BTreeMap::new(),
matches_cli_filter: false,
pin_version: Some(Specifier::new(pin_version)),
selector,
snap_to: None,
variant: VersionGroupVariant::Pinned,
};
}
if let Some(policy) = &group.policy {
if policy == "sameRange" {
return VersionGroup {
dependencies: BTreeMap::new(),
matches_cli_filter: false,
pin_version: None,
selector,
snap_to: None,
variant: VersionGroupVariant::SameRange,
};
} else if policy == "sameMinor" {
return VersionGroup {
dependencies: BTreeMap::new(),
matches_cli_filter: false,
pin_version: None,
selector,
snap_to: None,
variant: VersionGroupVariant::SameMinor,
};
} else {
panic!("Unrecognised version group policy: {policy}");
}
}
if let Some(snap_to) = &group.snap_to {
return VersionGroup {
dependencies: BTreeMap::new(),
matches_cli_filter: false,
pin_version: None,
selector,
snap_to: Some(
snap_to
.iter()
.flat_map(|name| {
packages.get_by_name(name).or_else(|| {
warn!("Invalid Snapped To Version Group: No package.json file found with a name property of '{name}'");
None
})
})
.collect(),
),
variant: VersionGroupVariant::SnappedTo,
};
}
if let Some(prefer_version) = &group.prefer_version {
return VersionGroup {
dependencies: BTreeMap::new(),
matches_cli_filter: false,
pin_version: None,
selector,
snap_to: None,
variant: if prefer_version == "lowestSemver" {
VersionGroupVariant::LowestSemver
} else {
VersionGroupVariant::HighestSemver
},
};
}
VersionGroup {
dependencies: BTreeMap::new(),
matches_cli_filter: false,
pin_version: None,
selector,
snap_to: None,
variant: VersionGroupVariant::HighestSemver,
}
}
pub fn get_sorted_dependencies(&self, sort: &SortBy) -> impl Iterator<Item = &Dependency> {
self
.dependencies
.values()
.filter(|dependency| dependency.matches_cli_filter)
.sorted_by(|a, b| match sort {
SortBy::Count => b.instances.len().cmp(&a.instances.len()),
SortBy::Name => Ordering::Equal,
})
}
pub fn get_update_urls(&self) -> Option<Vec<UpdateUrl>> {
match self.variant {
VersionGroupVariant::HighestSemver => Some(self.dependencies.values().filter_map(|dep| dep.get_update_url()).collect()),
_ => None,
}
}
pub fn has_ignored_variant(&self) -> bool {
matches!(self.variant, VersionGroupVariant::Ignored)
}
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct AnyVersionGroup {
#[serde(default)]
pub dependencies: Vec<String>,
#[serde(default)]
pub dependency_types: Vec<String>,
#[serde(default)]
pub label: String,
#[serde(default)]
pub packages: Vec<String>,
#[serde(default)]
pub specifier_types: Vec<String>,
pub is_banned: Option<bool>,
pub is_ignored: Option<bool>,
pub pin_version: Option<String>,
pub policy: Option<String>,
pub snap_to: Option<Vec<String>>,
pub prefer_version: Option<String>,
#[serde(flatten)]
pub unknown_fields: HashMap<String, Value>,
}