1use 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#[derive(Args, Debug)]
16#[command(
17 about = "Install service dependencies",
18 long_about = "安装服务依赖。可以安装指定的服务包,或者安装 Actr.toml 中配置的所有依赖"
19)]
20pub struct InstallCommand {
21 #[arg(value_name = "PACKAGE")]
23 pub packages: Vec<String>,
24
25 #[arg(long)]
27 pub force: bool,
28
29 #[arg(long)]
31 pub force_update: bool,
32
33 #[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 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 let dependency_specs = if !self.packages.is_empty() {
51 println!("📦 添加 {} 个新的服务依赖", self.packages.len());
53 self.parse_new_packages()?
54 } else {
55 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 let install_pipeline = {
73 let mut container = context.container.lock().unwrap();
74 container.get_install_pipeline()?
75 };
76
77 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 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 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 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 fn is_actr_project(&self) -> bool {
146 std::path::Path::new("Actr.toml").exists()
147 }
148
149 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 fn parse_package_spec(&self, package_spec: &str) -> Result<DependencySpec> {
163 if package_spec.starts_with("actr://") {
164 self.parse_actr_uri(package_spec)
166 } else if package_spec.contains('@') {
167 self.parse_versioned_spec(package_spec)
169 } else {
170 self.parse_simple_spec(package_spec)
172 }
173 }
174
175 fn parse_actr_uri(&self, uri: &str) -> Result<DependencySpec> {
177 if !uri.starts_with("actr://") {
179 return Err(anyhow::anyhow!("Invalid actr:// URI: {uri}"));
180 }
181
182 let uri_part = &uri[7..]; 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 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 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 _ => {} }
218 }
219 }
220
221 Ok((version, fingerprint))
222 } else {
223 Ok((None, None))
224 }
225 }
226
227 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 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 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 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}