actr_cli/commands/
install.rs

1//! Install 命令实现
2//!
3//! 基于复用架构实现 check-first 原则的安装流程
4
5use anyhow::Result;
6use async_trait::async_trait;
7use clap::Args;
8
9use crate::core::{
10    ActrCliError, Command, CommandContext, CommandResult, ComponentType, DependencySpec,
11    ErrorReporter, InstallResult,
12};
13
14/// Install 命令
15#[derive(Args, Debug)]
16#[command(
17    about = "Install service dependencies",
18    long_about = "安装服务依赖。可以安装指定的服务包,或者安装 Actr.toml 中配置的所有依赖"
19)]
20pub struct InstallCommand {
21    /// 要安装的服务包列表(例如:actr://user-service@1.0.0/)
22    #[arg(value_name = "PACKAGE")]
23    pub packages: Vec<String>,
24
25    /// 强制重新安装
26    #[arg(long)]
27    pub force: bool,
28
29    /// 强制更新所有依赖
30    #[arg(long)]
31    pub force_update: bool,
32
33    /// 跳过指纹验证
34    #[arg(long)]
35    pub skip_verification: bool,
36}
37
38#[async_trait]
39impl Command for InstallCommand {
40    async fn execute(&self, context: &CommandContext) -> Result<CommandResult> {
41        // 🔍 Check-First 原则:先验证项目状态
42        if !self.is_actr_project() {
43            return Err(ActrCliError::InvalidProject {
44                message: "Not an Actor-RTC project. Run 'actr init' to initialize.".to_string(),
45            }
46            .into());
47        }
48
49        // 确定安装模式
50        let dependency_specs = if !self.packages.is_empty() {
51            // 模式1: 添加新依赖 (npm install <package>)
52            println!("📦 添加 {} 个新的服务依赖", self.packages.len());
53            self.parse_new_packages()?
54        } else {
55            // 模式2: 安装配置中的依赖 (npm install)
56            if self.force_update {
57                println!("📦 强制更新配置中的所有服务依赖");
58            } else {
59                println!("📦 安装配置中的服务依赖");
60            }
61            self.load_dependencies_from_config(context).await?
62        };
63
64        if dependency_specs.is_empty() {
65            println!("ℹ️ 没有需要安装的依赖");
66            return Ok(CommandResult::Success(
67                "No dependencies to install".to_string(),
68            ));
69        }
70
71        // 获取安装管道(自动包含 ValidationPipeline)
72        let install_pipeline = {
73            let mut container = context.container.lock().unwrap();
74            container.get_install_pipeline()?
75        };
76
77        // 🚀 执行 check-first 安装流程
78        match install_pipeline
79            .install_dependencies(&dependency_specs)
80            .await
81        {
82            Ok(install_result) => {
83                self.display_install_success(&install_result);
84                Ok(CommandResult::Install(install_result))
85            }
86            Err(e) => {
87                // 友好的错误显示
88                let cli_error = ActrCliError::InstallFailed {
89                    reason: e.to_string(),
90                };
91                eprintln!("{}", ErrorReporter::format_error(&cli_error));
92                Err(e)
93            }
94        }
95    }
96
97    fn required_components(&self) -> Vec<ComponentType> {
98        // Install 命令需要完整的安装管道组件
99        vec![
100            ComponentType::ConfigManager,
101            ComponentType::DependencyResolver,
102            ComponentType::ServiceDiscovery,
103            ComponentType::NetworkValidator,
104            ComponentType::FingerprintValidator,
105            ComponentType::ProtoProcessor,
106            ComponentType::CacheManager,
107        ]
108    }
109
110    fn name(&self) -> &str {
111        "install"
112    }
113
114    fn description(&self) -> &str {
115        "npm风格的服务级依赖管理 (check-first 架构)"
116    }
117}
118
119impl InstallCommand {
120    pub fn new(
121        packages: Vec<String>,
122        force: bool,
123        force_update: bool,
124        skip_verification: bool,
125    ) -> Self {
126        Self {
127            packages,
128            force,
129            force_update,
130            skip_verification,
131        }
132    }
133
134    // 从 clap Args 创建
135    pub fn from_args(args: &InstallCommand) -> Self {
136        InstallCommand {
137            packages: args.packages.clone(),
138            force: args.force,
139            force_update: args.force_update,
140            skip_verification: args.skip_verification,
141        }
142    }
143
144    /// 检查是否在 Actor-RTC 项目中
145    fn is_actr_project(&self) -> bool {
146        std::path::Path::new("Actr.toml").exists()
147    }
148
149    /// 解析新包规范
150    fn parse_new_packages(&self) -> Result<Vec<DependencySpec>> {
151        let mut specs = Vec::new();
152
153        for package_spec in &self.packages {
154            let spec = self.parse_package_spec(package_spec)?;
155            specs.push(spec);
156        }
157
158        Ok(specs)
159    }
160
161    /// 解析单个包规范
162    fn parse_package_spec(&self, package_spec: &str) -> Result<DependencySpec> {
163        if package_spec.starts_with("actr://") {
164            // 直接 actr:// URI
165            self.parse_actr_uri(package_spec)
166        } else if package_spec.contains('@') {
167            // service-name@version 格式
168            self.parse_versioned_spec(package_spec)
169        } else {
170            // 简单服务名
171            self.parse_simple_spec(package_spec)
172        }
173    }
174
175    /// 解析 actr:// URI
176    fn parse_actr_uri(&self, uri: &str) -> Result<DependencySpec> {
177        // 简化的URI解析,实际实现应该更严格
178        if !uri.starts_with("actr://") {
179            return Err(anyhow::anyhow!("Invalid actr:// URI: {uri}"));
180        }
181
182        let uri_part = &uri[7..]; // Remove "actr://"
183        let service_name = if let Some(pos) = uri_part.find('/') {
184            uri_part[..pos].to_string()
185        } else {
186            uri_part.to_string()
187        };
188
189        // 提取查询参数(简化版本)
190        let (version, fingerprint) = if uri.contains('?') {
191            self.parse_query_params(uri)?
192        } else {
193            (None, None)
194        };
195
196        Ok(DependencySpec {
197            name: service_name,
198            uri: uri.to_string(),
199            version,
200            fingerprint,
201        })
202    }
203
204    /// 解析查询参数
205    fn parse_query_params(&self, uri: &str) -> Result<(Option<String>, Option<String>)> {
206        if let Some(query_start) = uri.find('?') {
207            let query = &uri[query_start + 1..];
208            let mut version = None;
209            let mut fingerprint = None;
210
211            for param in query.split('&') {
212                if let Some((key, value)) = param.split_once('=') {
213                    match key {
214                        "version" => version = Some(value.to_string()),
215                        "fingerprint" => fingerprint = Some(value.to_string()),
216                        _ => {} // 忽略未知参数
217                    }
218                }
219            }
220
221            Ok((version, fingerprint))
222        } else {
223            Ok((None, None))
224        }
225    }
226
227    /// 解析版本化规范 (service@version)
228    fn parse_versioned_spec(&self, spec: &str) -> Result<DependencySpec> {
229        let parts: Vec<&str> = spec.split('@').collect();
230        if parts.len() != 2 {
231            return Err(anyhow::anyhow!(
232                "Invalid package specification: {spec}. Use 'service-name@version'"
233            ));
234        }
235
236        let service_name = parts[0].to_string();
237        let version = parts[1].to_string();
238        let uri = format!("actr://{service_name}/?version={version}");
239
240        Ok(DependencySpec {
241            name: service_name,
242            uri,
243            version: Some(version),
244            fingerprint: None,
245        })
246    }
247
248    /// 解析简单规范 (service-name)
249    fn parse_simple_spec(&self, spec: &str) -> Result<DependencySpec> {
250        let service_name = spec.to_string();
251        let uri = format!("actr://{service_name}/");
252
253        Ok(DependencySpec {
254            name: service_name,
255            uri,
256            version: None,
257            fingerprint: None,
258        })
259    }
260
261    /// 从配置文件加载依赖
262    async fn load_dependencies_from_config(
263        &self,
264        context: &CommandContext,
265    ) -> Result<Vec<DependencySpec>> {
266        let config_manager = {
267            let container = context.container.lock().unwrap();
268            container.get_config_manager()?
269        };
270        let config = config_manager
271            .load_config(
272                config_manager
273                    .get_project_root()
274                    .join("Actr.toml")
275                    .as_path(),
276            )
277            .await?;
278
279        let mut specs = Vec::new();
280
281        if let Some(dependencies) = &config.dependencies {
282            for (name, dep_config) in dependencies {
283                let spec = match dep_config {
284                    crate::core::DependencyConfig::Simple(uri) => DependencySpec {
285                        name: name.clone(),
286                        uri: uri.clone(),
287                        version: None,
288                        fingerprint: None,
289                    },
290                    crate::core::DependencyConfig::Complex {
291                        uri,
292                        version,
293                        fingerprint,
294                    } => DependencySpec {
295                        name: name.clone(),
296                        uri: uri.clone(),
297                        version: version.clone(),
298                        fingerprint: fingerprint.clone(),
299                    },
300                };
301                specs.push(spec);
302            }
303        }
304
305        Ok(specs)
306    }
307
308    /// 显示安装成功信息
309    fn display_install_success(&self, result: &InstallResult) {
310        println!();
311        println!("✅ 安装成功!");
312        println!("   📦 安装的依赖: {}", result.installed_dependencies.len());
313        println!("   🗂️  缓存更新: {}", result.cache_updates);
314
315        if result.updated_config {
316            println!("   📝 已更新配置文件");
317        }
318
319        if result.updated_lock_file {
320            println!("   🔒 已更新锁文件");
321        }
322
323        if !result.warnings.is_empty() {
324            println!();
325            println!("⚠️ 警告:");
326            for warning in &result.warnings {
327                println!("   • {warning}");
328            }
329        }
330
331        println!();
332        println!("💡 建议: 运行 'actr gen' 生成最新的代码");
333    }
334}
335
336impl Default for InstallCommand {
337    fn default() -> Self {
338        Self::new(Vec::new(), false, false, false)
339    }
340}
341
342#[cfg(test)]
343mod tests {
344    use super::*;
345
346    #[test]
347    fn test_parse_simple_spec() {
348        let cmd = InstallCommand::default();
349        let spec = cmd.parse_simple_spec("user-service").unwrap();
350
351        assert_eq!(spec.name, "user-service");
352        assert_eq!(spec.uri, "actr://user-service/");
353        assert_eq!(spec.version, None);
354        assert_eq!(spec.fingerprint, None);
355    }
356
357    #[test]
358    fn test_parse_versioned_spec() {
359        let cmd = InstallCommand::default();
360        let spec = cmd.parse_versioned_spec("user-service@1.2.0").unwrap();
361
362        assert_eq!(spec.name, "user-service");
363        assert_eq!(spec.uri, "actr://user-service/?version=1.2.0");
364        assert_eq!(spec.version, Some("1.2.0".to_string()));
365        assert_eq!(spec.fingerprint, None);
366    }
367
368    #[test]
369    fn test_parse_actr_uri_simple() {
370        let cmd = InstallCommand::default();
371        let spec = cmd.parse_actr_uri("actr://user-service/").unwrap();
372
373        assert_eq!(spec.name, "user-service");
374        assert_eq!(spec.uri, "actr://user-service/");
375        assert_eq!(spec.version, None);
376        assert_eq!(spec.fingerprint, None);
377    }
378
379    #[test]
380    fn test_parse_actr_uri_with_params() {
381        let cmd = InstallCommand::default();
382        let spec = cmd
383            .parse_actr_uri("actr://user-service/?version=1.2.0&fingerprint=sha256:abc123")
384            .unwrap();
385
386        assert_eq!(spec.name, "user-service");
387        assert_eq!(
388            spec.uri,
389            "actr://user-service/?version=1.2.0&fingerprint=sha256:abc123"
390        );
391        assert_eq!(spec.version, Some("1.2.0".to_string()));
392        assert_eq!(spec.fingerprint, Some("sha256:abc123".to_string()));
393    }
394}