use anyhow::{Context, Result, bail};
use clap::Args;
use sha2::Digest;
use crate::cli::CmdCtx;
use crate::cmd::registry::account::{load_credentials_from_ctx, resolve_registry_url};
use dkp_core::registry::types::LockFile;
#[derive(Args, Debug)]
pub struct UpdateArgs {
pub name: Option<String>,
#[arg(long, value_name = "KEY", env = "DKP_REGISTRY_TOKEN")]
pub token: Option<String>,
#[arg(long)]
pub accept_new_key: bool,
}
pub async fn run(args: UpdateArgs, cli: &CmdCtx) -> Result<()> {
let lock_path = std::env::current_dir()?.join("dkp.lock");
if !lock_path.exists() {
println!("No dkp.lock found. Run 'dkp install <pack>' first.");
return Ok(());
}
let mut lock: LockFile = serde_json::from_str(&std::fs::read_to_string(&lock_path)?)?;
let base = resolve_registry_url(&cli.config.registry.url);
let token = args.token.or_else(|| {
load_credentials_from_ctx(&cli.config.registry.url)
.ok()?
.map(|(_, t)| t)
});
let client = dkp_core::registry::RegistryClient::new(base, token);
let packs_to_update: Vec<String> = match &args.name {
Some(n) => vec![n.clone()],
None => lock.resolved.keys().cloned().collect(),
};
let mut updated = 0;
for pack_name in &packs_to_update {
let locked = match lock.resolved.get(pack_name) {
Some(l) => l.clone(),
None => {
eprintln!("Warning: {pack_name} not in dkp.lock — skipping");
continue;
}
};
let versions = client
.list_versions(pack_name)
.await
.with_context(|| format!("failed to fetch versions for {pack_name}"))?;
let latest = match &versions.latest {
Some(v) => v.clone(),
None => {
println!("{pack_name}: no non-yanked version available");
continue;
}
};
if latest == locked.version {
println!("{pack_name}: already at latest ({latest})");
continue;
}
println!("{pack_name}: {} -> {}", locked.version, latest);
let meta = client.resolve(pack_name, &latest).await?;
if let Some(pinned) = &locked.publisher_public_key {
if pinned != &meta.publisher_public_key && !args.accept_new_key {
bail!(
"publisher key for '{pack_name}' changed since it was last pinned.\n \
pinned: {pinned}\n fetched: {}\n\
This could mean the publisher legitimately rotated their key, or that the \
registry response has been tampered with. If you've verified the rotation \
out-of-band, re-run with --accept-new-key.",
meta.publisher_public_key
);
}
if pinned != &meta.publisher_public_key {
eprintln!(
"Warning: accepting new publisher key for '{pack_name}' (--accept-new-key)."
);
}
}
let integrity = format!(
"sha256-{}",
hex::encode(sha2::Sha256::digest(meta.checksums.to_string().as_bytes()))
);
lock.resolved.insert(
pack_name.clone(),
dkp_core::registry::types::LockedPack {
version: latest,
archive_format: meta.archive_format,
integrity,
publisher_public_key: Some(meta.publisher_public_key),
},
);
updated += 1;
}
if updated > 0 {
std::fs::write(&lock_path, serde_json::to_string_pretty(&lock)?)?;
println!("\ndkp.lock updated ({updated} pack(s) updated). Run 'dkp install' to apply.");
} else {
println!("All packs are up to date.");
}
Ok(())
}