diff --git a/crates/cli/Cargo.toml b/crates/cli/Cargo.toml index d65aaed5..327251e7 100644 --- a/crates/cli/Cargo.toml +++ b/crates/cli/Cargo.toml @@ -13,6 +13,7 @@ path = "src/main.rs" [dependencies] log = "0.4.20" +ansi_term = "0.12.1" anyhow = "1.0" colored = "2.1.0" dirs = "5.0.1" @@ -23,7 +24,7 @@ env_logger = "0.11.2" serde = { version = "1.0", features = ["derive"] } tokio = { version = "1.0.0", features = ["full"] } clap = { version = "4.5.7", features = ["derive","cargo"] } -ansi_term = "0.12.1" +reqwest = { version = "0.12.3", features = ["json", "blocking", "stream","native-tls-vendored"] } regex = "1.10.4" glob = "0.3.1" clap_complete = "4.5.1" diff --git a/crates/cli/src/execute_cli.rs b/crates/cli/src/execute_cli.rs index df8a9dd2..7a62d5e2 100644 --- a/crates/cli/src/execute_cli.rs +++ b/crates/cli/src/execute_cli.rs @@ -1,12 +1,13 @@ use snm_config::SnmConfig; use snm_ni::trait_transform::IArgs; use snm_ni::{CommandArgsCreatorTrait, NpmArgsTransform, PnpmArgsTransform, YarnArgsTransform}; -use snm_node::snm_node::SnmNode; +use snm_node::snm_node::NodeAtom; use snm_utils::snm_error::SnmError; use std::process::{Command, Stdio}; use crate::fig::fig_spec_impl; use crate::manage_command::ManageCommands; +// use crate::node_manager::node_manager::NodeManager; use crate::snm_command::SnmCommands; use crate::SnmCli; @@ -14,7 +15,8 @@ pub async fn execute_cli(cli: SnmCli, snm_config: SnmConfig) -> Result<(), SnmEr match cli.command { // manage start SnmCommands::Node { command } => { - let snm_node = SnmNode::new(snm_config); + let snm_node = NodeAtom::new(snm_config); + // let x = NodeManager::new(); match command { ManageCommands::Default { version } => { snm_node.set_default(version.as_str()).await?; diff --git a/crates/cli/src/lib.rs b/crates/cli/src/lib.rs index 11896e00..9423d54e 100644 --- a/crates/cli/src/lib.rs +++ b/crates/cli/src/lib.rs @@ -3,6 +3,7 @@ use snm_command::SnmCommands; pub mod fig; pub mod manage_command; +// pub mod node_manager; pub mod snm_command; pub mod execute_cli; diff --git a/crates/cli/src/main.rs b/crates/cli/src/main.rs index 8658ae49..c5fb519d 100644 --- a/crates/cli/src/main.rs +++ b/crates/cli/src/main.rs @@ -1,10 +1,13 @@ use std::env::current_dir; use clap::Parser; +// use node_manager::NodeManager; use snm::{execute_cli::execute_cli, SnmCli}; use snm_config::parse_snm_config; use snm_utils::snm_error::{friendly_error_message, SnmError}; +pub mod node_manager; + #[tokio::main] async fn main() { // color_backtrace::install(); diff --git a/crates/cli/src/node_manager/mod.rs b/crates/cli/src/node_manager/mod.rs new file mode 100644 index 00000000..6bbf3b22 --- /dev/null +++ b/crates/cli/src/node_manager/mod.rs @@ -0,0 +1,3 @@ +pub mod node_manager; +pub mod node_model; +pub mod node_schedule; diff --git a/crates/cli/src/node_manager/node_manager.rs b/crates/cli/src/node_manager/node_manager.rs new file mode 100644 index 00000000..bf21fe61 --- /dev/null +++ b/crates/cli/src/node_manager/node_manager.rs @@ -0,0 +1,201 @@ +use dialoguer::Confirm; +use snm_core::traits::atom::AtomTrait; +use snm_download_builder::{DownloadBuilder, WriteStrategy}; +use snm_utils::snm_error::SnmError; +use std::{collections::HashMap, fs, ops::Not as _, path::PathBuf, time::Duration}; + +use super::{node_model::NodeModel, node_schedule::NodeSchedule}; + +pub struct NodeManager<'a, T: AtomTrait> { + node_atom: &'a T, +} + +impl<'a, T> NodeManager<'a, T> +where + T: AtomTrait, +{ + async fn internal_download(&self, version: &str) -> Result<(), SnmError> { + let download_url = self.node_atom.get_download_url(version); + let downloaded_file_path_buf = self.node_atom.get_downloaded_file_path_buf(version)?; + + DownloadBuilder::new() + .retries(3) + .write_strategy(WriteStrategy::Nothing) + .download(&download_url, &downloaded_file_path_buf) + .await?; + + let runtime = self.node_atom.get_runtime_dir_path_buf(version)?; + + if runtime.exists() { + fs::remove_dir_all(&runtime)?; + } + + self.node_atom + .decompress_download_file(&downloaded_file_path_buf, &runtime)?; + + fs::remove_file(&downloaded_file_path_buf)?; + + Ok(()) + } + + fn internal_set_default(&self, version: &str) -> Result { + let default_dir = self.node_atom.get_runtime_dir_for_default_path_buf()?; + if default_dir.exists() { + fs::remove_dir_all(&default_dir)?; + } + + let from_dir = self.node_atom.get_runtime_dir_path_buf(version)?; + + #[cfg(unix)] + { + std::os::unix::fs::symlink(&from_dir, &default_dir)?; + } + #[cfg(windows)] + { + std::os::windows::fs::symlink_dir(&version_dir, &default_dir)?; + } + Ok(default_dir) + } + + async fn get_node_list_remote(&self) -> Result, SnmError> { + let host = self.node_atom.get_snm_config().get_node_dist_url(); + let node_list_url = format!("{}/index.json", host); + + let client = reqwest::Client::new(); + + let node_vec: Vec = client + .get(&node_list_url) + .timeout(Duration::from_secs(10)) + .send() + .await? + .json::>() + .await?; + Ok(node_vec) + } + + async fn get_node_schedule(&self) -> Result, SnmError> { + let host = self + .node_atom + .get_snm_config() + .get_node_github_resource_host(); + + let node_schedule_url = format!("{}/nodejs/Release/main/schedule.json", host); + + let client = reqwest::Client::new(); + + let node_schedule_vec: Vec = client + .get(&node_schedule_url) + .timeout(Duration::from_secs(10)) + .send() + .await? + .json::>() + .await? + .into_iter() + .map(|(v, mut schedule)| { + schedule.version = Some(v[1..].to_string()); + schedule + }) + .collect(); + + Ok(node_schedule_vec) + } + + async fn get_node_sha256_hashmap( + &self, + node_version: &str, + ) -> Result, SnmError> { + let host = self.node_atom.get_snm_config().get_node_dist_url(); + let url = format!("{}/v{}/SHASUMS256.txt", host, node_version); + + let sha256_str = reqwest::get(&url).await?.text().await?; + + let sha256_map: std::collections::HashMap = sha256_str + .lines() + .map(|line| { + let mut iter = line.split_whitespace(); + let sha256 = iter.next().unwrap(); + let file = iter.next().unwrap(); + (file.to_string(), sha256.to_string()) + }) + .collect(); + + Ok(sha256_map) + } +} + +impl<'a, T> NodeManager<'a, T> +where + T: AtomTrait, +{ + pub fn new(node_atom: &'a T) -> Self { + Self { node_atom } + } + + pub async fn set_default(&self, version: &str) -> Result<(), SnmError> { + if self + .node_atom + .get_anchor_file_path_buf(version)? + .exists() + .not() + { + let msg = format!( + "🤔 v{} is not installed, do you want to install it ?", + version + ); + if Confirm::new().with_prompt(msg).interact()? { + self.install(version).await?; + } + } + + self.internal_set_default(version)?; + + Ok(()) + } + + pub async fn install(&self, version: &str) -> Result<(), SnmError> { + let anchor_file = self.node_atom.get_anchor_file_path_buf(&version)?; + let version_dir = self.node_atom.get_runtime_dir_path_buf(&version)?; + + if anchor_file.exists() { + let confirm = Confirm::new() + .with_prompt(format!( + "🤔 v{} is already installed, do you want to reinstall it ?", + &version + )) + .interact()?; + + if confirm { + fs::remove_dir_all(&version_dir)?; + self.internal_download(version).await?; + } + } else { + self.internal_download(version).await?; + } + + self.internal_set_default(version)?; + + Ok(()) + } + + pub async fn un_install(&self, version: &str) -> Result<(), SnmError> { + let default_dir = self.node_atom.get_runtime_dir_for_default_path_buf()?; + let version_dir = self.node_atom.get_runtime_dir_path_buf(&version)?; + if fs::read_link(&default_dir)?.eq(&version_dir) { + let msg = format!( + "🤔 {} is default instance, do you want to uninstall it ?", + version + ); + if Confirm::new().with_prompt(msg).interact()? { + fs::remove_file(&default_dir)?; + fs::remove_dir_all(version_dir)?; + } + } else { + fs::remove_dir_all(version_dir)?; + } + Ok(()) + } + + pub async fn list(&self) -> Result<(), SnmError> { + Ok(()) + } +} diff --git a/crates/cli/src/node_manager/node_model.rs b/crates/cli/src/node_manager/node_model.rs new file mode 100644 index 00000000..04946f58 --- /dev/null +++ b/crates/cli/src/node_manager/node_model.rs @@ -0,0 +1,76 @@ +use serde::{Deserialize, Deserializer, Serialize}; +use std::{borrow::Cow, fmt}; + +#[derive(Deserialize, Debug, Serialize)] +pub struct NodeModel { + pub version: String, + pub date: String, + pub files: Vec, + pub npm: Option, + pub v8: String, + pub uv: Option, + pub zlib: Option, + pub openssl: Option, + pub modules: Option, + pub lts: Lts, + pub security: bool, + pub end: Option, + pub current: Option, + pub deprecated: Option, +} + +impl fmt::Display for NodeModel { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!( + f, + "version: {:<10}, lts: {:<10}, date: {:<12}, end: {:<12}, npm: {:<14}, v8: {:<14}, uv: {:<12}, zlib: {:<18}, openssl: {:<14}, modules: {:<12}, deprecated: {}", + self.version, + match &self.lts { + Lts::Str(s) => Cow::Borrowed(s), + Lts::Bool(b) => Cow::Owned(b.to_string()), + }, + self.date, + self.end.as_deref().unwrap_or("None"), + self.npm.as_deref().unwrap_or("None"), + self.v8, + self.uv.as_deref().unwrap_or("None"), + self.zlib.as_deref().unwrap_or("None"), + self.openssl.as_deref().unwrap_or("None"), + self.modules.as_deref().unwrap_or("None"), + self.deprecated.map_or("None".to_string(), |v| v.to_string()) + ) + } +} + +#[derive(Debug)] +pub enum Lts { + Str(String), + Bool(bool), +} + +impl Serialize for Lts { + fn serialize(&self, serializer: S) -> Result + where + S: serde::ser::Serializer, + { + match self { + Lts::Str(s) => serializer.serialize_str(s), + Lts::Bool(b) => serializer.serialize_bool(*b), + } + } +} + +// 自定义反序列化 +impl<'de> Deserialize<'de> for Lts { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let value = serde_json::Value::deserialize(deserializer)?; + match value { + serde_json::Value::String(s) => Ok(Lts::Str(s)), + serde_json::Value::Bool(b) => Ok(Lts::Bool(b)), + _ => Err(serde::de::Error::custom("expected a string or a bool")), + } + } +} diff --git a/crates/cli/src/node_manager/node_schedule.rs b/crates/cli/src/node_manager/node_schedule.rs new file mode 100644 index 00000000..3b7388a4 --- /dev/null +++ b/crates/cli/src/node_manager/node_schedule.rs @@ -0,0 +1,37 @@ +use colored::*; +use core::fmt; +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Serialize, Deserialize)] +pub struct NodeSchedule { + pub start: String, + pub end: String, + pub maintenance: Option, + pub lts: Option, + pub codename: Option, + pub version: Option, +} + +impl fmt::Display for NodeSchedule { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let lts = self + .lts + .as_deref() + .map_or_else(|| format!(""), |lts| format!("Lts by {:<12}", lts)); + + let codename = self.codename.as_deref().map_or_else( + || format!("{:<20}", ""), + |codename| format!("{:<10} {:<10}", codename.bright_black(), lts.bright_black()), + ); + + write!( + f, + "Create by: {:<12}, Death by: {:<12}, Maintenance By {:<12}, Version {:<5} {}", + self.start.bright_green(), + self.end.bright_magenta(), + self.maintenance.as_deref().unwrap_or("none").bright_green(), + self.version.as_deref().unwrap_or("none").bright_green(), + codename, + ) + } +} diff --git a/crates/snm_node/src/snm_node.rs b/crates/snm_node/src/snm_node.rs index 4256c4a9..c39e9277 100644 --- a/crates/snm_node/src/snm_node.rs +++ b/crates/snm_node/src/snm_node.rs @@ -31,11 +31,11 @@ use std::{ }; use tokio::try_join; -pub struct SnmNode { +pub struct NodeAtom { snm_config: SnmConfig, } -impl SnmNode { +impl NodeAtom { async fn download(&self, version: &str) -> Result<(), SnmError> { let download_url = self.get_download_url(version); let downloaded_file_path_buf = self.get_downloaded_file_path_buf(version)?; @@ -460,7 +460,7 @@ impl SnmNode { } } -impl AtomTrait for SnmNode { +impl AtomTrait for NodeAtom { fn get_anchor_file_path_buf(&self, version: &str) -> Result { self.snm_config .get_node_bin_dir()? diff --git a/crates/snm_package_manager/src/snm_package_manager.rs b/crates/snm_package_manager/src/snm_package_manager.rs index be3b2f73..b31f5e93 100644 --- a/crates/snm_package_manager/src/snm_package_manager.rs +++ b/crates/snm_package_manager/src/snm_package_manager.rs @@ -18,12 +18,12 @@ use std::{ path::PathBuf, }; -pub struct SnmPackageManager { +pub struct PackageManagerAtom { snm_config: SnmConfig, library_name: String, } -impl SnmPackageManager { +impl PackageManagerAtom { pub fn new(library_name: &str, snm_config: SnmConfig) -> Self { Self { library_name: library_name.to_string(), @@ -32,7 +32,7 @@ impl SnmPackageManager { } } -impl AtomTrait for SnmPackageManager { +impl AtomTrait for PackageManagerAtom { fn get_anchor_file_path_buf(&self, v: &str) -> Result { self.snm_config .get_node_modules_dir()? @@ -163,7 +163,6 @@ impl AtomTrait for SnmPackageManager { decompress(&input_file_path_buf, &output_dir_path_buf)?; if let Some(package_json) = parse_package_json(&output_dir_path_buf)? { let bin = output_dir_path_buf.join("bin"); - if bin.exists().not() { fs::create_dir_all(&bin)?; } diff --git a/crates/snm_shim/src/get_node_bin_dir.rs b/crates/snm_shim/src/get_node_bin_dir.rs index 59a822fd..d5187bc4 100644 --- a/crates/snm_shim/src/get_node_bin_dir.rs +++ b/crates/snm_shim/src/get_node_bin_dir.rs @@ -2,7 +2,7 @@ use std::env::current_dir; use snm_config::parse_snm_config; use snm_core::traits::atom::AtomTrait; -use snm_node::snm_node::SnmNode; +use snm_node::snm_node::NodeAtom; use snm_utils::snm_error::SnmError; use tracing::instrument; @@ -14,7 +14,7 @@ pub async fn get_node_bin_dir() -> Result { let snm_config = parse_snm_config(&dir)?; - let snm_node = SnmNode::new(snm_config.clone()); + let snm_node = NodeAtom::new(snm_config.clone()); let get_default_version = || -> Result { if snm_config.get_strict() { diff --git a/crates/snm_shim/src/lib.rs b/crates/snm_shim/src/lib.rs index bcee02c4..ec0d3987 100644 --- a/crates/snm_shim/src/lib.rs +++ b/crates/snm_shim/src/lib.rs @@ -7,7 +7,7 @@ use crate::get_default_bin_dir::get_default_bin_dir; use crate::get_node_bin_dir::get_node_bin_dir; use ensure_binary_path::ensure_binary_path; use snm_config::parse_snm_config; -use snm_package_manager::snm_package_manager::SnmPackageManager; +use snm_package_manager::snm_package_manager::PackageManagerAtom; use snm_utils::{exec::exec_cli, snm_error::SnmError}; use std::env::{self, current_dir}; use tracing_subscriber::{self}; @@ -29,7 +29,7 @@ pub async fn load_package_manage_shim(prefix: &str, bin_name: &str) -> Result<() let snm_config = parse_snm_config(&dir)?; - let snm_package_manage = SnmPackageManager::new(prefix, snm_config.clone()); + let snm_package_manage = PackageManagerAtom::new(prefix, snm_config.clone()); let restricted_list = vec!["install", "i", "run"];