actr_cli/commands/
install.rs

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