use {
crate::{
context::Context,
instance::Instance,
instance_state::InstanceState,
package_json::PackageJson,
specifier::{semver_range::SemverRange, Specifier},
version_group::VersionGroupVariant,
},
itertools::Itertools,
std::{cell::RefCell, cmp::Ordering, collections::HashMap, rc::Rc, vec},
};
#[cfg(test)]
#[path = "dependency_test.rs"]
mod dependency_test;
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct UpdateUrl {
pub internal_name: String,
pub url: String,
}
#[derive(Debug)]
pub struct Dependency {
pub expected: RefCell<Option<Specifier>>,
pub has_alias: bool,
pub instances: Vec<Rc<Instance>>,
pub local_instance: RefCell<Option<Rc<Instance>>>,
pub matches_cli_filter: bool,
pub internal_name: String,
pub pinned_specifier: Option<Specifier>,
pub snapped_to_packages: Option<Vec<Rc<RefCell<PackageJson>>>>,
pub variant: VersionGroupVariant,
}
impl Dependency {
pub fn new(
internal_name: String,
variant: VersionGroupVariant,
pinned_specifier: Option<Specifier>,
snapped_to_packages: Option<Vec<Rc<RefCell<PackageJson>>>>,
) -> Dependency {
Dependency {
expected: RefCell::new(None),
has_alias: false,
instances: vec![],
local_instance: RefCell::new(None),
matches_cli_filter: false,
internal_name,
pinned_specifier,
snapped_to_packages,
variant,
}
}
pub fn get_update_url(&self) -> Option<UpdateUrl> {
if self.matches_cli_filter && self.internal_name_is_supported() {
self.instances.iter().find_map(|instance| instance.get_update_url())
} else {
None
}
}
pub fn add_instance(&mut self, instance: Rc<Instance>) {
self.instances.push(Rc::clone(&instance));
if instance.is_local {
*self.local_instance.borrow_mut() = Some(Rc::clone(&instance));
}
}
pub fn get_state(&self) -> InstanceState {
self
.instances
.iter()
.fold(InstanceState::Unknown, |acc, instance| acc.max(instance.state.borrow().clone()))
}
pub fn get_states(&self) -> Vec<InstanceState> {
self
.instances
.iter()
.map(|instance| instance.state.borrow().clone())
.collect::<Vec<InstanceState>>()
}
pub fn set_expected_specifier(&self, specifier: &Specifier) -> &Self {
*self.expected.borrow_mut() = Some(specifier.clone());
self
}
pub fn get_local_specifier(&self) -> Option<Specifier> {
self
.local_instance
.borrow()
.as_ref()
.map(|instance| instance.descriptor.specifier.clone())
}
fn internal_name_is_supported(&self) -> bool {
!self.internal_name.contains('>') && self.internal_name.rfind('@').unwrap_or(0) == 0
}
pub fn has_local_instance(&self) -> bool {
self.local_instance.borrow().is_some()
}
pub fn has_local_instance_with_invalid_specifier(&self) -> bool {
self.get_local_specifier().is_some_and(|local| {
if let Specifier::BasicSemver(semver) = local {
!matches!(semver.range_variant, SemverRange::Exact)
} else {
true
}
})
}
pub fn every_specifier_is_already_identical(&self) -> bool {
if let Some(first_actual) = self.instances.first().map(|instance| &instance.descriptor.specifier) {
self.instances.iter().all(|instance| instance.descriptor.specifier == *first_actual)
} else {
false
}
}
pub fn get_unique_specifiers(&self) -> Vec<Specifier> {
let mut unique_specifiers = Vec::new();
for instance in self.instances.iter() {
if !unique_specifiers.contains(&instance.descriptor.specifier) {
unique_specifiers.push(instance.descriptor.specifier.clone());
}
}
unique_specifiers
}
pub fn get_highest_or_lowest_specifier(&self) -> Option<Specifier> {
let prefer_highest = matches!(self.variant, VersionGroupVariant::HighestSemver);
let preferred_order = if prefer_highest { Ordering::Greater } else { Ordering::Less };
self
.get_instances()
.filter(|instance| instance.descriptor.specifier.get_node_version().is_some())
.map(|instance| instance.descriptor.specifier.clone())
.fold(None, |preferred, specifier| match preferred {
None => Some(specifier),
Some(preferred) => {
if specifier.get_node_version().cmp(&preferred.get_node_version()) == preferred_order {
Some(specifier)
} else {
Some(preferred)
}
}
})
}
pub fn get_eligible_registry_updates(&self, ctx: &Context) -> Option<HashMap<Specifier, Vec<Specifier>>> {
ctx.updates_by_internal_name.get(&self.internal_name).map(|updates| {
let mut specifiers_by_eligible_update: HashMap<Specifier, Vec<Specifier>> = HashMap::new();
self.get_unique_specifiers().iter().for_each(|installed| {
updates
.iter()
.filter(|update| update.is_eligible_update_for(installed, &ctx.config.cli.target))
.filter(|update| installed.has_same_release_channel_as(update))
.fold(None, |preferred, specifier| match preferred {
None => Some(specifier),
Some(preferred) => {
if specifier.get_node_version().cmp(&preferred.get_node_version()) == Ordering::Greater {
Some(specifier)
} else {
Some(preferred)
}
}
})
.inspect(|highest_update| {
let affected = specifiers_by_eligible_update.entry((*highest_update).clone()).or_default();
affected.push(installed.clone());
});
});
specifiers_by_eligible_update
})
}
pub fn get_snapped_to_specifier(&self, every_instance_in_the_project: &[Rc<Instance>]) -> Option<Specifier> {
if let Some(snapped_to_packages) = &self.snapped_to_packages {
for instance in every_instance_in_the_project {
if *instance.descriptor.internal_name == *self.internal_name {
for snapped_to_package in snapped_to_packages {
if instance.descriptor.package.borrow().name == snapped_to_package.borrow().name {
return Some(instance.descriptor.specifier.clone());
}
}
}
}
}
None
}
pub fn get_instances(&self) -> impl Iterator<Item = &Rc<Instance>> {
self.instances.iter().filter(|instance| instance.descriptor.matches_cli_filter)
}
pub fn get_sorted_instances(&self) -> impl Iterator<Item = &Rc<Instance>> {
self.get_instances().sorted_by(|a, b| {
if a.is_valid() && !b.is_valid() {
return Ordering::Less;
}
if b.is_valid() && !a.is_valid() {
return Ordering::Greater;
}
if a.has_missing_specifier() {
return Ordering::Greater;
}
if b.has_missing_specifier() {
return Ordering::Less;
}
let specifier_order = b.descriptor.specifier.cmp(&a.descriptor.specifier);
if matches!(specifier_order, Ordering::Equal) {
a.descriptor.package.borrow().name.cmp(&b.descriptor.package.borrow().name)
} else {
specifier_order
}
})
}
}