CLI: propagate alias rename errors, doctor config resilience

aliases: rename now propagates every meta.json I/O and parse error
instead of silently ignoring failures. Previously, a corrupt or
unreadable meta.json after directory rename would leave the alias
in an inconsistent state with no user-visible error.

doctor: Config::load failure now falls back to Config::default()
with a warning instead of aborting the entire health check. Doctor
should still run diagnostics even when config is missing or corrupt
— that's exactly when you need it most.
This commit is contained in:
teernisse
2026-02-12 16:14:01 -05:00
parent 75d9344b44
commit 0b9a8a36c5
3 changed files with 29 additions and 16 deletions

View File

@@ -304,16 +304,22 @@ fn cmd_rename(names: &[String], robot: bool, start: Instant) -> Result<(), Swagg
))
})?;
// Update meta.json alias field
// Update meta.json alias field -- propagate errors so the cache
// doesn't end up with a stale alias name in metadata.
let meta_path = new_dir.join("meta.json");
if let Ok(bytes) = std::fs::read(&meta_path)
&& let Ok(mut meta) = serde_json::from_slice::<CacheMetadata>(&bytes)
{
meta.alias = new.clone();
if let Ok(updated_bytes) = serde_json::to_vec_pretty(&meta) {
let _ = std::fs::write(&meta_path, updated_bytes);
}
}
let meta_bytes = std::fs::read(&meta_path).map_err(|e| {
SwaggerCliError::Cache(format!("failed to read meta.json after rename: {e}"))
})?;
let mut meta: CacheMetadata = serde_json::from_slice(&meta_bytes).map_err(|e| {
SwaggerCliError::Cache(format!("failed to parse meta.json after rename: {e}"))
})?;
meta.alias = new.clone();
let updated_bytes = serde_json::to_vec_pretty(&meta).map_err(|e| {
SwaggerCliError::Cache(format!("failed to serialize meta.json after rename: {e}"))
})?;
std::fs::write(&meta_path, updated_bytes).map_err(|e| {
SwaggerCliError::Cache(format!("failed to write meta.json after rename: {e}"))
})?;
// Update config if old was the default
let cfg_path = config_path(None);

View File

@@ -32,8 +32,8 @@ pub struct Args {
#[arg(long)]
pub prune_stale: bool,
/// Days before an alias is considered stale (default: 90)
#[arg(long, default_value_t = 90)]
/// Days before an alias is considered stale (default: 30, matching config)
#[arg(long, default_value_t = 30)]
pub prune_threshold: u32,
/// Evict least-recently-used aliases until total size is under this limit (MB)
@@ -290,13 +290,13 @@ fn execute_prune(args: &Args, robot: bool, start: Instant) -> Result<(), Swagger
"cache",
start.elapsed(),
);
} else if stale.is_empty() {
} else if pruned.is_empty() {
println!(
"No stale aliases (threshold: {} days).",
args.prune_threshold
);
} else {
println!("Pruned {} stale alias(es).", stale.len());
println!("Pruned {} stale alias(es).", pruned.len());
}
Ok(())
}

View File

@@ -344,12 +344,19 @@ fn try_fix_alias(cm: &CacheManager, alias: &str) -> Result<Vec<String>, Vec<Stri
pub async fn execute(args: &Args, robot_mode: bool) -> Result<(), SwaggerCliError> {
let start = Instant::now();
// Load config
// Load config -- doctor should work even when config is missing or
// corrupt, so fall back to defaults and report a warning.
let cfg_path = config_path(None);
let config = Config::load(&cfg_path)?;
let mut warnings: Vec<String> = Vec::new();
let config = match Config::load(&cfg_path) {
Ok(cfg) => cfg,
Err(e) => {
warnings.push(format!("could not load config (using defaults): {e}"));
Config::default()
}
};
// Check config dir exists
let mut warnings: Vec<String> = Vec::new();
if let Some(parent) = cfg_path.parent()
&& !parent.exists()
{