use std::path::Path;
use crate::error::{CliError, Result};
use sherpack_core::{LoadedPack, ResolvePolicy, Values};
use sherpack_repo::{
CredentialStore, DependencyResolver, LockFile, RepositoryConfig, create_backend,
filter_dependencies,
};
pub async fn list(pack_path: &Path) -> Result<()> {
let pack = LoadedPack::load(pack_path).map_err(|e| CliError::input(e.to_string()))?;
if pack.pack.dependencies.is_empty() {
println!("No dependencies defined in Pack.yaml");
return Ok(());
}
let values = Values::from_file(&pack.values_path)
.map(|v| v.into_inner())
.unwrap_or_else(|_| serde_json::json!({}));
println!("Dependencies for {}:", pack.pack.metadata.name);
println!();
for dep in &pack.pack.dependencies {
let alias_info = dep
.alias
.as_ref()
.map(|a| format!(" (alias: {})", a))
.unwrap_or_default();
let status = if !dep.enabled {
" [disabled]"
} else if dep.resolve == ResolvePolicy::Never {
" [resolve: never]"
} else if dep.condition.is_some() && !dep.should_resolve(&values) {
" [condition: false]"
} else {
""
};
println!(" {} @ {}{}{}", dep.name, dep.version, alias_info, status);
println!(" repository: {}", dep.repository);
if let Some(condition) = &dep.condition {
println!(" condition: {}", condition);
}
if dep.resolve != ResolvePolicy::WhenEnabled {
println!(" resolve: {:?}", dep.resolve);
}
if !dep.enabled {
println!(" enabled: false");
}
}
let filter_result = filter_dependencies(&pack.pack.dependencies, &values);
if filter_result.has_skipped() {
println!();
println!("Skipped dependencies ({}):", filter_result.skipped.len());
println!("{}", filter_result.skipped_summary());
}
let lock_path = pack_path.join("Pack.lock.yaml");
if lock_path.exists() {
println!();
println!("Lock file: Pack.lock.yaml (exists)");
let pack_yaml_content =
std::fs::read_to_string(pack_path.join("Pack.yaml")).unwrap_or_default();
if let Ok(lock) = LockFile::load(&lock_path)
&& lock.is_outdated(&pack_yaml_content)
{
println!(" WARNING: Lock file is outdated. Run 'sherpack dependency update'");
}
} else {
println!();
println!("Lock file: not found");
println!(" Run 'sherpack dependency update' to create one");
}
Ok(())
}
pub async fn update(pack_path: &Path) -> Result<()> {
let pack = LoadedPack::load(pack_path).map_err(|e| CliError::input(e.to_string()))?;
if pack.pack.dependencies.is_empty() {
println!("No dependencies to resolve");
return Ok(());
}
println!("Resolving dependencies for {}...", pack.pack.metadata.name);
let values = Values::from_file(&pack.values_path)
.map(|v| v.into_inner())
.unwrap_or_else(|_| serde_json::json!({}));
let filter_result = filter_dependencies(&pack.pack.dependencies, &values);
if filter_result.has_skipped() {
println!();
println!("Skipping {} dependencies:", filter_result.skipped.len());
println!("{}", filter_result.skipped_summary());
}
if filter_result.to_resolve.is_empty() {
println!();
println!("No dependencies to resolve (all skipped)");
let pack_yaml_content =
std::fs::read_to_string(pack_path.join("Pack.yaml")).map_err(CliError::io)?;
let lock = LockFile::new(&pack_yaml_content);
let lock_path = pack_path.join("Pack.lock.yaml");
lock.save(&lock_path)
.map_err(|e| CliError::internal(e.to_string()))?;
println!("Wrote empty Pack.lock.yaml");
return Ok(());
}
let config = RepositoryConfig::load().map_err(|e| CliError::internal(e.to_string()))?;
let cred_store = CredentialStore::load().unwrap_or_default();
let resolver = DependencyResolver::new(|repo_url, name, version| {
let rt = tokio::runtime::Handle::current();
rt.block_on(async {
let repo = if let Some(r) = config.repositories.iter().find(|r| r.url == repo_url) {
r.clone()
} else {
sherpack_repo::Repository::new("_temp", repo_url)?
};
let credentials = cred_store.get(&repo.name).and_then(|c| c.resolve().ok());
let mut backend = create_backend(repo, credentials).await?;
backend.find_best_match(name, version).await
})
});
let graph = resolver
.resolve(&filter_result.to_resolve)
.map_err(|e| CliError::internal(e.to_string()))?;
println!();
println!("Resolved {} dependencies:", graph.len());
for dep in graph.iter() {
let alias_info = dep
.alias
.as_ref()
.map(|a| format!(" (alias: {})", a))
.unwrap_or_default();
println!(" {} @ {}{}", dep.name, dep.version, alias_info);
}
println!();
println!("Dependency tree:");
println!("{}", graph.render_tree());
let pack_yaml_content =
std::fs::read_to_string(pack_path.join("Pack.yaml")).map_err(CliError::io)?;
let lock = graph.to_lock_file(&pack_yaml_content);
let lock_path = pack_path.join("Pack.lock.yaml");
lock.save(&lock_path)
.map_err(|e| CliError::internal(e.to_string()))?;
println!();
println!(
"Wrote Pack.lock.yaml with {} locked dependencies",
graph.len()
);
Ok(())
}
pub async fn build(pack_path: &Path, verify: bool) -> Result<()> {
let _pack = LoadedPack::load(pack_path).map_err(|e| CliError::input(e.to_string()))?;
let lock_path = pack_path.join("Pack.lock.yaml");
if !lock_path.exists() {
return Err(CliError::input(
"Pack.lock.yaml not found. Run 'sherpack dependency update' first",
));
}
let lock = LockFile::load(&lock_path).map_err(|e| CliError::internal(e.to_string()))?;
let pack_yaml_content =
std::fs::read_to_string(pack_path.join("Pack.yaml")).map_err(CliError::io)?;
if lock.is_outdated(&pack_yaml_content) {
return Err(CliError::input(
"Pack.lock.yaml is outdated. Run 'sherpack dependency update' first",
));
}
if lock.dependencies.is_empty() {
println!("No dependencies to download");
return Ok(());
}
let config = RepositoryConfig::load().map_err(|e| CliError::internal(e.to_string()))?;
let cred_store = CredentialStore::load().unwrap_or_default();
let charts_dir = pack_path.join("charts");
std::fs::create_dir_all(&charts_dir)?;
println!("Downloading {} dependencies...", lock.dependencies.len());
for locked in &lock.dependencies {
print!(" {} @ {}... ", locked.name, locked.version);
let repo = if let Some(r) = config
.repositories
.iter()
.find(|r| r.url == locked.repository)
{
r.clone()
} else {
sherpack_repo::Repository::new("_temp", &locked.repository)
.map_err(|e| CliError::internal(e.to_string()))?
};
let credentials = cred_store.get(&repo.name).and_then(|c| c.resolve().ok());
let backend = create_backend(repo, credentials)
.await
.map_err(|e| CliError::internal(e.to_string()))?;
let data = backend
.download(&locked.name, &locked.version.to_string())
.await
.map_err(|e| CliError::internal(e.to_string()))?;
if verify {
match lock.verify(locked.effective_name(), &data) {
Ok(sherpack_repo::VerifyResult::Match) => {
print!("verified... ");
}
Ok(sherpack_repo::VerifyResult::DigestChanged { .. }) => {
print!("(digest changed)... ");
}
Err(e) => {
println!("FAILED");
return Err(CliError::internal(format!("Integrity check failed: {}", e)));
}
}
}
let dest = charts_dir.join(locked.effective_name());
extract_archive(&data, &dest)?;
println!("OK");
}
println!();
println!(
"Downloaded {} dependencies to charts/",
lock.dependencies.len()
);
Ok(())
}
pub async fn tree(pack_path: &Path) -> Result<()> {
let pack = LoadedPack::load(pack_path).map_err(|e| CliError::input(e.to_string()))?;
let lock_path = pack_path.join("Pack.lock.yaml");
if !lock_path.exists() {
println!("{}@{}", pack.pack.metadata.name, pack.pack.metadata.version);
for (i, dep) in pack.pack.dependencies.iter().enumerate() {
let is_last = i == pack.pack.dependencies.len() - 1;
let prefix = if is_last { "└── " } else { "├── " };
println!("{}{} @ {}", prefix, dep.name, dep.version);
}
println!();
println!("Note: Run 'sherpack dependency update' to resolve transitive dependencies");
return Ok(());
}
let lock = LockFile::load(&lock_path).map_err(|e| CliError::internal(e.to_string()))?;
let resolver = DependencyResolver::new(|_, _, _| {
Err(sherpack_repo::RepoError::Other(
"Tree display only".to_string(),
))
});
let graph = resolver
.resolve_from_lock(&lock)
.map_err(|e| CliError::internal(e.to_string()))?;
println!("{}@{}", pack.pack.metadata.name, pack.pack.metadata.version);
println!("{}", graph.render_tree());
Ok(())
}
fn extract_archive(data: &[u8], dest: &std::path::PathBuf) -> Result<()> {
use flate2::read::GzDecoder;
use tar::Archive;
let gz = GzDecoder::new(std::io::Cursor::new(data));
let mut archive = Archive::new(gz);
std::fs::create_dir_all(dest)?;
archive.unpack(dest)?;
Ok(())
}