use super::TextJsonOutputFormat;
use super::cli::CacheScope;
use crate::cache::CacheStatus;
use crate::error::{RailError, RailResult};
use std::path::Path;
pub(crate) fn run_status(workspace_root: &Path, scope: CacheScope, format: TextJsonOutputFormat) -> RailResult<()> {
if format.is_json() {
crate::output::set_json_mode(true);
}
let status = crate::cache::status(workspace_root, scope.includes_workspace(), scope.includes_local())?;
if format.is_json() {
let output = crate::output::machine_json_envelope(
"cache",
"status",
"success",
0,
serde_json::json!({ "scope": scope.as_str(), "status": status }),
);
println!("{}", serde_json::to_string_pretty(&output)?);
} else {
render_status(&status);
}
Ok(())
}
pub(crate) fn run_clean(
workspace_root: &Path,
scope: CacheScope,
check: bool,
format: TextJsonOutputFormat,
) -> RailResult<()> {
if format.is_json() {
crate::output::set_json_mode(true);
}
if check {
let status = crate::cache::status(workspace_root, scope.includes_workspace(), scope.includes_local())?;
let pending = has_state(&status);
if format.is_json() {
let output = crate::output::machine_json_envelope(
"cache",
"clean_check",
if pending { "pending_changes" } else { "success" },
if pending { 1 } else { 0 },
serde_json::json!({
"scope": scope.as_str(),
"would_reclaim_bytes": total_bytes(&status),
"status": status,
}),
);
println!("{}", serde_json::to_string_pretty(&output)?);
} else {
render_status(&status);
if pending {
println!("\ncache state would be reclaimed; run without --check to apply");
} else {
println!("\nnothing to reclaim");
}
}
return if pending {
Err(RailError::CheckHasPendingChanges)
} else {
Ok(())
};
}
if scope.includes_workspace() && scope.includes_local() {
crate::cache::status(workspace_root, true, false)?;
}
let mut removal = crate::cache::CacheRemoval::default();
if scope.includes_local() {
removal.extend(crate::cache::remove_local()?)?;
}
if scope.includes_workspace() {
removal.extend(crate::cache::remove_workspace(workspace_root)?)?;
}
if format.is_json() {
let output = crate::output::machine_json_envelope(
"cache",
"clean",
"success",
0,
serde_json::json!({
"scope": scope.as_str(),
"reclaimed_bytes": removal.bytes,
"removed": removal.paths,
}),
);
println!("{}", serde_json::to_string_pretty(&output)?);
} else if removal.paths.is_empty() {
println!("nothing to reclaim");
} else {
println!(
"reclaimed {} bytes from {} cache path(s)",
removal.bytes,
removal.paths.len()
);
}
Ok(())
}
fn has_state(status: &CacheStatus) -> bool {
status.workspace.as_ref().is_some_and(|workspace| {
workspace
.artifacts
.iter()
.any(|artifact| artifact.kind != "workspace_cache_lock")
}) || status.local.as_ref().is_some_and(|local| local.present)
}
fn total_bytes(status: &CacheStatus) -> u64 {
status
.workspace
.as_ref()
.map_or(0, |workspace| workspace.bytes)
.saturating_add(
status
.local
.as_ref()
.and_then(|local| local.cache.as_ref())
.map_or(0, |local| local.bytes),
)
}
fn render_status(status: &CacheStatus) {
println!("cache status");
if let Some(workspace) = &status.workspace {
println!(" workspace");
println!(" root: {}", workspace.root);
println!(" bytes: {}", workspace.bytes);
println!(" files: {}", workspace.files);
println!(" directories: {}", workspace.directories);
println!(" fully bounded: {}", workspace.fully_bounded);
for artifact in &workspace.artifacts {
let bound = artifact
.max_bytes
.map_or_else(|| "unbounded".to_string(), |bytes| bytes.to_string());
println!(
" {}: {} bytes (bound: {}, path: {})",
artifact.kind, artifact.bytes, bound, artifact.path
);
}
}
if let Some(local) = &status.local {
println!(" local (shared across workspaces)");
if let Some(cache) = &local.cache {
println!(" root: {}", cache.root);
println!(" bytes: {} / {}", cache.bytes, cache.max_bytes);
println!(" results: {}", cache.results);
println!(" objects: {}", cache.objects);
println!(" pins: {}", cache.pins);
println!(" active leases: {}", cache.active_leases);
println!(" stale leases: {}", cache.stale_leases);
println!(
" staging: {} entries / {} bytes",
cache.staging_entries, cache.staging_bytes
);
println!(" reclaimable bytes: {}", cache.reclaimable_bytes);
if let Some(oldest) = cache.oldest_used_unix_ms {
println!(" oldest use (unix ms): {oldest}");
}
if let Some(newest) = cache.newest_used_unix_ms {
println!(" newest use (unix ms): {newest}");
}
} else {
println!(" absent");
}
}
}