1use colored::Colorize;
2use regex::Regex;
3use std::env;
4use std::path::Path;
5use tokio::fs::{read_to_string, write};
6
7use crate::utils::registries::Registry;
8use clap::{Parser, Subcommand};
9
10use super::{registries::Store, Logger};
11
12#[derive(Parser, Debug)]
13#[command(name = "rust-nrm")]
14#[command(version = "0.2.1")]
15#[command(about = "A Rust-based NPM Registry Manager 🦀")]
16#[command(
17 long_about = "RNRM helps you easily switch between different npm registries. It supports both global and local registry configuration."
18)]
19pub struct Cli {
20 #[command(subcommand)]
21 pub command: Commands,
22}
23
24#[derive(Subcommand, Debug)]
25pub enum Commands {
26 #[command(about = "List all available registries")]
28 #[command(
29 long_about = "Display a list of all configured registries with their URLs. Currently active registries (global/local) will be highlighted."
30 )]
31 Ls,
32
33 #[command(about = "Switch to a different registry")]
35 #[command(
36 long_about = "Change the active npm registry. Use --local flag to change only the current directory's registry."
37 )]
38 Use {
39 #[arg(required = true, value_name = "REGISTRY")]
41 registry: String,
42
43 #[arg(short, long, default_value_t = false)]
45 local: bool,
46 },
47
48 #[command(about = "Test registry response times")]
50 #[command(
51 long_about = "Measure and compare response times for all configured registries to help you choose the fastest one."
52 )]
53 Test,
54
55 #[command(about = "Add a new registry")]
57 #[command(
58 long_about = "Add a custom registry with its URL and optional homepage. The registry will be available for use immediately."
59 )]
60 Add {
61 #[arg(required = true, value_name = "NAME")]
63 registry: String,
64
65 #[arg(required = true, value_name = "URL")]
67 url: String,
68
69 #[arg(value_name = "HOMEPAGE")]
71 home: Option<String>,
72 },
73
74 #[command(about = "Remove a registry", alias = "rm")]
76 #[command(
77 long_about = "Remove a registry from the configuration. Built-in registries cannot be removed."
78 )]
79 Remove {
80 #[arg(required = true, value_name = "REGISTRY")]
82 registry: String,
83 },
84}
85
86pub struct CommandExecutor {
87 store: Store,
88}
89
90impl CommandExecutor {
91 pub fn new(store: Store) -> Self {
92 Self { store }
93 }
94
95 pub async fn execute(&mut self, command: Commands) {
96 match command {
97 Commands::Ls => self.handle_list().await,
98 Commands::Use { registry, local } => self.handle_use(registry, local).await,
99 Commands::Test => self.handle_test().await,
100 Commands::Add { registry, url, home } => self.handle_add(registry, url, home).await,
101 Commands::Remove { registry } => self.handle_remove(registry).await,
102 }
103 }
104
105 async fn handle_list(&mut self) {
106 self.store.list_registries().await;
107 }
108
109 async fn handle_use(&mut self, registry: String, local: bool) {
110 if let Some(registry_data) = self.store.registries.get(®istry) {
111 let registry_text = format!("registry={}", registry_data.registry);
112 let npmrc_path = if local {
113 ".npmrc".to_string()
114 } else {
115 let home_dir = env::var("HOME").expect("Failed to get HOME directory");
116 format!("{}/.npmrc", home_dir)
117 };
118
119 if let Ok(current_dir) = env::current_dir() {
120 let absolute_path = if local {
121 current_dir.join(&npmrc_path)
122 } else {
123 Path::new(&npmrc_path).to_path_buf()
124 };
125 println!(
126 "{} {}",
127 format!("Absolute path of .npmrc:").blue().bold(),
128 format!("{}", absolute_path.display())
129 );
130 }
131
132 if Path::new(&npmrc_path).exists() {
133 let content = read_to_string(&npmrc_path).await.unwrap();
134 let re = Regex::new(r"(?m)^\s*registry\s*=\s*.*$").unwrap();
135 let updated_content = re.replace_all(&content, ®istry_text).to_string();
136 write(&npmrc_path, updated_content).await.unwrap();
137 } else {
138 write(&npmrc_path, registry_text).await.unwrap();
139 }
140
141 self.store.save().await;
142 Logger::success(&format!(
143 "{} registry updated!",
144 if local { "Local" } else { "Global" }
145 ));
146 } else {
147 Logger::error("Registry not found!");
148 }
149 }
150
151 async fn handle_test(&mut self) {
152 self.store.test_registry_speed().await;
153 }
154
155 async fn handle_add(&mut self, registry: String, url: String, home: Option<String>) {
156 self.store.registries.insert(
157 registry.clone(),
158 Registry {
159 registry: url.clone(),
160 home,
161 },
162 );
163
164 self.store.save().await;
165 Logger::success(&format!(
166 "Registry {} added with URL: {}",
167 registry.green().bold(),
168 url.yellow()
169 ));
170 }
171
172 async fn handle_remove(&mut self, registry: String) {
173 if let Some(removed) = self.store.registries.remove(®istry) {
174 self.store.save().await;
175 Logger::success(&format!(
176 "Registry {} removed (URL: {})",
177 registry.green().bold(),
178 removed.registry.yellow()
179 ));
180 } else {
181 Logger::error(&format!(
182 "Registry {} not found",
183 registry.red().bold()
184 ));
185 }
186 }
187}