1use anyhow::{bail, Context, Result};
2use clap::Args;
3use ggen_core::{CacheManager, LockfileManager, RegistryClient};
4use std::env;
5
6#[derive(Args, Debug)]
7pub struct AddArgs {
8 pub rpack_id: String,
10}
11
12pub async fn run(args: &AddArgs) -> Result<()> {
13 let (pack_id, version) = parse_rpack_spec(&args.rpack_id)?;
14
15 let current_dir = env::current_dir().context("Failed to get current directory")?;
17
18 let registry_client = RegistryClient::new()?;
20 let cache_manager = CacheManager::new()?;
21 let lockfile_manager = LockfileManager::new(¤t_dir);
22
23 if lockfile_manager.is_installed(&pack_id)? {
25 println!("Rpack '{}' is already installed", pack_id);
26 return Ok(());
27 }
28
29 println!("Resolving rpack '{}'...", pack_id);
31 let resolved_pack = registry_client
32 .resolve(&pack_id, version.as_deref())
33 .await
34 .with_context(|| format!("Failed to resolve rpack '{}'", pack_id))?;
35
36 println!(
37 "Found rpack '{}' version {}",
38 resolved_pack.id, resolved_pack.version
39 );
40
41 println!("Downloading rpack...");
43 let cached_pack = cache_manager
44 .ensure(&resolved_pack)
45 .await
46 .with_context(|| format!("Failed to download rpack '{}'", pack_id))?;
47
48 println!("Cached rpack to: {}", cached_pack.path.display());
49
50 println!("Updating lockfile...");
52 lockfile_manager.upsert(
53 &resolved_pack.id,
54 &resolved_pack.version,
55 &resolved_pack.sha256,
56 &resolved_pack.git_url,
57 )?;
58
59 println!(
60 "✅ Successfully added rpack '{}' version {}",
61 resolved_pack.id, resolved_pack.version
62 );
63
64 if let Some(manifest) = &cached_pack.manifest {
66 if !manifest.templates.patterns.is_empty() {
67 println!("\nAvailable template patterns:");
68 for pattern in &manifest.templates.patterns {
69 println!(" - {}:{}", pack_id, pattern);
70 }
71 }
72 }
73
74 Ok(())
75}
76
77fn parse_rpack_spec(spec: &str) -> Result<(String, Option<String>)> {
78 if spec.is_empty() {
79 bail!("Invalid rpack spec: empty string. Expected format: <rpack-id>[@version]");
80 }
81
82 if let Some(at_pos) = spec.rfind('@') {
83 let id = spec[..at_pos].to_string();
84 let version = spec[at_pos + 1..].to_string();
85
86 if id.is_empty() || version.is_empty() {
87 bail!(
88 "Invalid rpack spec: '{}'. Expected format: <rpack-id>[@version]",
89 spec
90 );
91 }
92
93 Ok((id, Some(version)))
94 } else {
95 Ok((spec.to_string(), None))
96 }
97}
98
99#[cfg(test)]
100mod tests {
101 use super::*;
102
103 #[test]
104 fn test_parse_rpack_spec() {
105 let (id, version) = parse_rpack_spec("io.rgen.test").unwrap();
106 assert_eq!(id, "io.rgen.test");
107 assert_eq!(version, None);
108
109 let (id, version) = parse_rpack_spec("io.rgen.test@0.1.0").unwrap();
110 assert_eq!(id, "io.rgen.test");
111 assert_eq!(version, Some("0.1.0".to_string()));
112 }
113
114 #[test]
115 fn test_parse_rpack_spec_invalid() {
116 assert!(parse_rpack_spec("@0.1.0").is_err());
117 assert!(parse_rpack_spec("io.rgen.test@").is_err());
118 assert!(parse_rpack_spec("").is_err());
119 }
120}