use crate::fs_paths::Paths;
use crate::git::GitCli;
pub fn cache_dir_name(repo: &str) -> String {
repo.replace('/', "--")
}
pub fn manifest_json<G: GitCli>(git: &G, paths: &Paths, repo: &str) -> anyhow::Result<String> {
crate::progress::step(&format!("fetching {repo}"));
let dest = paths.index_repos_dir().join(cache_dir_name(repo));
let _ = std::fs::remove_dir_all(&dest);
if let Some(parent) = dest.parent() {
std::fs::create_dir_all(parent)?;
}
let url = format!("https://github.com/{repo}.git");
git.sparse_fetch(&url, &dest, ".claude-plugin")?;
let manifest = dest.join(".claude-plugin/marketplace.json");
Ok(std::fs::read_to_string(manifest)?)
}
#[cfg(test)]
mod tests {
use super::*;
use std::cell::RefCell;
use std::path::Path;
struct FakeGit { calls: RefCell<Vec<(String, String)>> }
impl GitCli for FakeGit {
fn clone(&self, _u: &str, _d: &Path) -> anyhow::Result<()> { Ok(()) }
fn pull(&self, _r: &Path) -> anyhow::Result<()> { Ok(()) }
fn head_sha(&self, _r: &Path) -> anyhow::Result<String> { Ok("sha".into()) }
fn checkout(&self, _r: &Path, _g: &str) -> anyhow::Result<()> { Ok(()) }
fn is_repo(&self, _r: &Path) -> bool { true }
fn sparse_fetch(&self, u: &str, d: &Path, _s: &str) -> anyhow::Result<()> {
self.calls.borrow_mut().push((u.into(), d.to_string_lossy().into()));
std::fs::create_dir_all(d.join(".claude-plugin")).unwrap();
std::fs::write(d.join(".claude-plugin/marketplace.json"),
r#"{ "name": "m", "plugins": [] }"#).unwrap();
Ok(())
}
}
#[test]
fn cache_dir_name_flattens_slash() {
assert_eq!(cache_dir_name("owner/repo"), "owner--repo");
}
#[test]
fn manifest_json_reports_the_repo_being_fetched() {
let tmp = tempfile::tempdir().unwrap();
let paths = crate::fs_paths::Paths::from_home(tmp.path().to_path_buf());
let git = FakeGit { calls: RefCell::new(vec![]) };
let (rec, _g) = crate::progress::record();
manifest_json(&git, &paths, "o/r").unwrap();
assert_eq!(rec.events().as_slice(), &["step: fetching o/r"]);
}
#[test]
fn fetches_via_sparse_when_no_local_clone() {
let tmp = std::env::temp_dir().join(format!("cpf-fetch-{}", std::process::id()));
let _ = std::fs::remove_dir_all(&tmp);
std::fs::create_dir_all(&tmp).unwrap();
let paths = Paths::from_home(tmp.clone());
let git = FakeGit { calls: RefCell::new(vec![]) };
let json = manifest_json(&git, &paths, "owner/repo").unwrap();
assert!(json.contains("\"name\": \"m\""));
assert_eq!(git.calls.borrow().len(), 1);
let _ = std::fs::remove_dir_all(&tmp);
}
}