use crate::actions::abstract_action::AbstractAction;
use crate::actions::{ActionInvocation, ActionKind, ActionSpec, action_spec};
use indexmap::IndexMap;
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct InfoAction;
impl InfoAction {
pub const fn new() -> Self {
Self
}
pub fn spec(&self) -> &'static ActionSpec {
action_spec(ActionKind::Info).expect("info action spec")
}
pub fn handle_invocation(&self) -> ActionInvocation {
<Self as AbstractAction>::handle(self, Vec::new(), Vec::new(), Vec::new())
}
pub fn create_plan(
&self,
system: SystemInformation,
package_manager: PackageManagerInformation,
cli_version: impl Into<String>,
dependencies: &[PackageJsonDependency],
) -> InfoActionPlan {
create_info_action_plan(system, package_manager, cli_version, dependencies)
}
}
impl AbstractAction for InfoAction {
fn kind(&self) -> ActionKind {
ActionKind::Info
}
}
pub const WARNING_MESSAGE_DEPENDENCIES_WHITE_LIST: &[&str] = &[
"@nestrs/core",
"@nestrs/common",
"@nestrs/schematics",
"@nestrs/platform-axum",
"@nestrs/platform-actix",
"@nestrs/platform-socket.io",
"@nestrs/platform-ws",
"@nestrs/websockets",
];
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct InfoActionPlan {
pub system: SystemInformation,
pub package_manager: PackageManagerInformation,
pub cli_version: String,
pub nest_dependencies: Vec<NestDependency>,
pub warnings: NestDependencyWarnings,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct SystemInformation {
pub os_version: String,
pub node_version: String,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct PackageManagerInformation {
pub name: String,
pub version: Option<String>,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct PackageJsonDependency {
pub package_name: String,
pub declared_version: String,
pub resolved_version: Option<String>,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct NestDependency {
pub name: String,
pub value: String,
pub package_name: String,
}
pub type NestDependencyWarnings = IndexMap<String, Vec<NestDependency>>;
pub fn create_info_action_plan(
system: SystemInformation,
package_manager: PackageManagerInformation,
cli_version: impl Into<String>,
dependencies: &[PackageJsonDependency],
) -> InfoActionPlan {
let nest_dependencies = build_nest_versions_message(dependencies);
let warnings = build_nest_versions_warning_message(&nest_dependencies);
InfoActionPlan {
system,
package_manager,
cli_version: cli_version.into(),
nest_dependencies,
warnings,
}
}
pub fn build_nest_versions_message(dependencies: &[PackageJsonDependency]) -> Vec<NestDependency> {
format(collect_nest_dependencies(dependencies))
}
pub fn collect_nest_dependencies(dependencies: &[PackageJsonDependency]) -> Vec<NestDependency> {
dependencies
.iter()
.filter(|dependency| dependency.package_name.contains("@nestrs"))
.map(|dependency| {
let value = dependency
.resolved_version
.clone()
.unwrap_or_else(|| dependency.declared_version.clone());
NestDependency {
name: format!(
"{} version",
dependency
.package_name
.replace("@nestrs/", "")
.split('@')
.next()
.unwrap_or(&dependency.package_name)
),
value,
package_name: dependency.package_name.clone(),
}
})
.collect()
}
pub fn format(mut dependencies: Vec<NestDependency>) -> Vec<NestDependency> {
dependencies.sort_by(|left, right| right.name.len().cmp(&left.name.len()));
let Some(length) = dependencies.first().map(|dependency| dependency.name.len()) else {
return dependencies;
};
dependencies
.into_iter()
.map(|mut dependency| {
dependency.name = right_pad(dependency.name, length);
dependency.name.push_str(" :");
dependency.value = dependency.value.replace(['^', '~'], "");
dependency
})
.collect()
}
pub fn build_nest_versions_warning_message(
nest_dependencies: &[NestDependency],
) -> NestDependencyWarnings {
let mut unsorted_warnings: NestDependencyWarnings = IndexMap::new();
for dependency in nest_dependencies {
if !WARNING_MESSAGE_DEPENDENCIES_WHITE_LIST.contains(&dependency.package_name.as_str()) {
continue;
}
let major = dependency
.value
.chars()
.filter(|character| character.is_ascii_digit() || *character == '.')
.collect::<String>()
.split('.')
.next()
.unwrap_or_default()
.to_string();
unsorted_warnings
.entry(major)
.or_default()
.push(dependency.clone());
}
if unsorted_warnings.len() <= 1 {
return IndexMap::new();
}
let mut entries = unsorted_warnings.into_iter().collect::<Vec<_>>();
entries.sort_by(|(left, _), (right, _)| compare_versions_desc(left, right));
entries.into_iter().collect()
}
pub fn right_pad(mut name: String, length: usize) -> String {
while name.len() < length {
name.push(' ');
}
name
}
fn compare_versions_desc(left: &str, right: &str) -> std::cmp::Ordering {
let left = left.parse::<f64>();
let right = right.parse::<f64>();
match (left, right) {
(Ok(left), Ok(right)) => right
.partial_cmp(&left)
.unwrap_or(std::cmp::Ordering::Equal),
(Err(_), Err(_)) => std::cmp::Ordering::Equal,
(Err(_), Ok(_)) => std::cmp::Ordering::Greater,
(Ok(_), Err(_)) => std::cmp::Ordering::Less,
}
}