Skip to main content

rust_nrm/utils/
cli.rs

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    /// List all available registries
27    #[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    /// Switch to a different registry
34    #[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        /// Name of the registry to use (e.g., npm, yarn, taobao)
40        #[arg(required = true, value_name = "REGISTRY")]
41        registry: String,
42
43        /// Apply changes only to the current directory
44        #[arg(short, long, default_value_t = false)]
45        local: bool,
46    },
47
48    /// Test registry response times
49    #[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    /// Add a new registry
56    #[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        /// Name for the new registry
62        #[arg(required = true, value_name = "NAME")]
63        registry: String,
64
65        /// Registry URL (e.g., https://registry.npmjs.org/)
66        #[arg(required = true, value_name = "URL")]
67        url: String,
68
69        /// Homepage URL for the registry
70        #[arg(value_name = "HOMEPAGE")]
71        home: Option<String>,
72    },
73
74    /// Remove a registry
75    #[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        /// Name of the registry to remove
81        #[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(&registry) {
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, &registry_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(&registry) {
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}