use clap_noun_verb::{NounVerbError, Result};
use clap_noun_verb_macros::verb;
use serde_json::{json, Value};
use std::path::PathBuf;
use ggen_core::domain::packs::capability_registry::{
list_capabilities, resolve_capability_to_packs,
};
use ggen_core::packs::lockfile::{LockedPack, PackLockfile, PackSource};
fn project_root() -> Result<PathBuf> {
std::env::current_dir()
.map_err(|e| NounVerbError::execution_error(format!("cannot resolve project dir: {}", e)))
}
fn atomic_packs_for(surface: &str, projection: Option<&str>, runtime: Option<&str>) -> Vec<String> {
let mut packs = resolve_capability_to_packs(surface, projection, runtime).unwrap_or_default();
if let Some(p) = projection {
let projection_pack = format!("projection-{}", p);
if !packs.contains(&projection_pack) {
packs.push(projection_pack);
}
}
packs
}
fn require_surface(surface: &str) -> Result<()> {
if surface.trim().is_empty() {
return Err(NounVerbError::argument_error(
"capability surface must not be empty",
));
}
Ok(())
}
#[verb]
pub fn enable(
#[arg(index = 1)] surface: String, projection: Option<String>, runtime: Option<String>,
) -> Result<Value> {
require_surface(&surface)?;
let packs = atomic_packs_for(&surface, projection.as_deref(), runtime.as_deref());
let root = project_root()?;
let lock_path = root.join(".ggen").join("packs.lock");
let mut lockfile = if lock_path.exists() {
PackLockfile::from_file(&lock_path)
.map_err(|e| NounVerbError::execution_error(format!("cannot read lockfile: {}", e)))?
} else {
PackLockfile::new(env!("CARGO_PKG_VERSION"))
};
for pid in &packs {
let digest = ggen_core::calculate_sha256(format!("{}@0.0.0", pid).as_bytes());
lockfile.add_pack(
pid,
LockedPack {
version: "0.0.0".to_string(),
source: PackSource::Registry {
url: "https://registry.ggen.io".to_string(),
},
integrity: Some(format!("sha256-{}", digest)),
installed_at: chrono::Utc::now(),
dependencies: Vec::new(),
},
);
}
lockfile
.save(&lock_path)
.map_err(|e| NounVerbError::execution_error(format!("cannot write lockfile: {}", e)))?;
Ok(json!({
"capability": surface,
"projection": projection,
"runtime": runtime,
"atomic_packs": packs,
"lockfile": lock_path.display().to_string(),
}))
}
#[verb]
pub fn list() -> Result<Value> {
let caps: Vec<Value> = list_capabilities()
.into_iter()
.map(|c| {
json!({
"id": c.id,
"name": c.name,
"description": c.description,
"category": c.category,
"atomic_packs": c.atomic_packs,
})
})
.collect();
Ok(json!({ "total": caps.len(), "capabilities": caps }))
}
#[verb]
pub fn inspect(#[arg(index = 1)] surface: String) -> Result<Value> {
require_surface(&surface)?;
let packs = atomic_packs_for(&surface, None, None);
Ok(json!({
"capability": surface,
"atomic_packs": packs,
}))
}