ggen_cli_lib/cmds/
add.rs

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    /// Rpack ID with optional version (e.g., "io.rgen.rust.cli-subcommand@0.2.0")
9    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    // Get current working directory
16    let current_dir = env::current_dir().context("Failed to get current directory")?;
17
18    // Initialize managers
19    let registry_client = RegistryClient::new()?;
20    let cache_manager = CacheManager::new()?;
21    let lockfile_manager = LockfileManager::new(&current_dir);
22
23    // Check if already installed
24    if lockfile_manager.is_installed(&pack_id)? {
25        println!("Rpack '{}' is already installed", pack_id);
26        return Ok(());
27    }
28
29    // Resolve pack from registry
30    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    // Download and cache the pack
42    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    // Update lockfile
51    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    // Show available templates if any
65    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}