use colored::Colorize;
use regex::Regex;
use std::env;
use std::path::Path;
use tokio::fs::{read_to_string, write};
use crate::utils::registries::Registry;
use clap::{Parser, Subcommand};
use super::{registries::Store, Logger};
#[derive(Parser, Debug)]
#[command(name = "rust-nrm")]
#[command(version = "0.1.3")]
#[command(about = "A Rust-based NPM Registry Manager 🦀")]
#[command(
long_about = "RNRM helps you easily switch between different npm registries. It supports both global and local registry configuration."
)]
pub struct Cli {
#[command(subcommand)]
pub command: Commands,
}
#[derive(Subcommand, Debug)]
pub enum Commands {
#[command(about = "List all available registries")]
#[command(
long_about = "Display a list of all configured registries with their URLs. Currently active registries (global/local) will be highlighted."
)]
Ls,
#[command(about = "Switch to a different registry")]
#[command(
long_about = "Change the active npm registry. Use --local flag to change only the current directory's registry."
)]
Use {
#[arg(required = true, value_name = "REGISTRY")]
registry: String,
#[arg(short, long, default_value_t = false)]
local: bool,
},
#[command(about = "Test registry response times")]
#[command(
long_about = "Measure and compare response times for all configured registries to help you choose the fastest one."
)]
Test,
#[command(about = "Add a new registry")]
#[command(
long_about = "Add a custom registry with its URL and optional homepage. The registry will be available for use immediately."
)]
Add {
#[arg(required = true, value_name = "NAME")]
registry: String,
#[arg(required = true, value_name = "URL")]
url: String,
#[arg(value_name = "HOMEPAGE")]
home: Option<String>,
},
#[command(about = "Remove a registry")]
#[command(
long_about = "Remove a registry from the configuration. Built-in registries cannot be removed."
)]
Remove {
#[arg(required = true, value_name = "REGISTRY")]
registry: String,
},
}
pub struct CommandExecutor {
store: Store,
}
impl CommandExecutor {
pub fn new(store: Store) -> Self {
Self { store }
}
pub async fn execute(&mut self, command: Commands) {
match command {
Commands::Ls => self.handle_list().await,
Commands::Use { registry, local } => self.handle_use(registry, local).await,
Commands::Test => self.handle_test().await,
Commands::Add { registry, url, home } => self.handle_add(registry, url, home).await,
Commands::Remove { registry } => self.handle_remove(registry).await,
}
}
async fn handle_list(&mut self) {
self.store.list_registries().await;
}
async fn handle_use(&mut self, registry: String, local: bool) {
if let Some(registry_data) = self.store.registries.get(®istry) {
let registry_text = format!("registry={}", registry_data.registry);
let npmrc_path = if local {
".npmrc".to_string()
} else {
let home_dir = env::var("HOME").expect("Failed to get HOME directory");
format!("{}/.npmrc", home_dir)
};
if let Ok(current_dir) = env::current_dir() {
let absolute_path = if local {
current_dir.join(&npmrc_path)
} else {
Path::new(&npmrc_path).to_path_buf()
};
println!(
"{} {}",
format!("Absolute path of .npmrc:").blue().bold(),
format!("{}", absolute_path.display())
);
}
if Path::new(&npmrc_path).exists() {
let content = read_to_string(&npmrc_path).await.unwrap();
let re = Regex::new(r"(?m)^\s*registry\s*=\s*.*$").unwrap();
let updated_content = re.replace_all(&content, ®istry_text).to_string();
write(&npmrc_path, updated_content).await.unwrap();
} else {
write(&npmrc_path, registry_text).await.unwrap();
}
self.store.save().await;
Logger::success(&format!(
"{} registry updated!",
if local { "Local" } else { "Global" }
));
} else {
Logger::error("Registry not found!");
}
}
async fn handle_test(&mut self) {
self.store.test_registry_speed().await;
}
async fn handle_add(&mut self, registry: String, url: String, home: Option<String>) {
self.store.registries.insert(
registry.clone(),
Registry {
registry: url.clone(),
home,
},
);
self.store.save().await;
Logger::success(&format!(
"Registry {} added with URL: {}",
registry.green().bold(),
url.yellow()
));
}
async fn handle_remove(&mut self, registry: String) {
if let Some(removed) = self.store.registries.remove(®istry) {
self.store.save().await;
Logger::success(&format!(
"Registry {} removed (URL: {})",
registry.green().bold(),
removed.registry.yellow()
));
} else {
Logger::error(&format!(
"Registry {} not found",
registry.red().bold()
));
}
}
}