1use anyhow::Result;
6use async_trait::async_trait;
7
8use crate::core::{
9 ActrCliError, Command, CommandContext, CommandResult, ComponentType, DependencySpec,
10 ErrorReporter, InstallResult,
11};
12
13pub 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 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 let dependency_specs = if !self.packages.is_empty() {
36 println!("📦 添加 {} 个新的服务依赖", self.packages.len());
38 self.parse_new_packages()?
39 } else {
40 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 let install_pipeline = {
58 let mut container = context.container.lock().unwrap();
59 container.get_install_pipeline()?
60 };
61
62 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 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 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 fn is_actr_project(&self) -> bool {
121 std::path::Path::new("Actr.toml").exists()
122 }
123
124 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 fn parse_package_spec(&self, package_spec: &str) -> Result<DependencySpec> {
138 if package_spec.starts_with("actr://") {
139 self.parse_actr_uri(package_spec)
141 } else if package_spec.contains('@') {
142 self.parse_versioned_spec(package_spec)
144 } else {
145 self.parse_simple_spec(package_spec)
147 }
148 }
149
150 fn parse_actr_uri(&self, uri: &str) -> Result<DependencySpec> {
152 if !uri.starts_with("actr://") {
154 return Err(anyhow::anyhow!("Invalid actr:// URI: {uri}"));
155 }
156
157 let uri_part = &uri[7..]; 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 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 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 _ => {} }
193 }
194 }
195
196 Ok((version, fingerprint))
197 } else {
198 Ok((None, None))
199 }
200 }
201
202 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 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 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 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}