use std::path::Path;
use colored::Colorize;
use xbp_deploy::{
classify_deploy_error, record_from_plan_with_version, DeployHistoryStore, DeployPlan,
DeployTarget, K8sPlanView, OciPlan, ServiceDeployPlan, ServicePlan,
};
pub fn is_xbp_ledger_noise_path(path: &str) -> bool {
let p = path.replace('\\', "/").trim().trim_start_matches("./").to_string();
let lower = p.to_ascii_lowercase();
if lower.contains("/.xbp/deployments/")
|| lower.starts_with(".xbp/deployments/")
|| lower.contains("/.xbp/releases/")
|| lower.starts_with(".xbp/releases/")
{
return true;
}
if lower.ends_with("/.xbp/last-version-scope")
|| lower == ".xbp/last-version-scope"
|| lower.ends_with("/.xbp/deploy.lock")
|| lower == ".xbp/deploy.lock"
|| lower.ends_with("/.xbp/opennext-deploy.log")
|| lower.ends_with("opennext-deploy.log")
{
return true;
}
if lower.contains("/.xbp/")
&& (lower.contains("deployments/")
|| lower.contains("releases/")
|| lower.ends_with("last-version-scope")
|| lower.ends_with("deploy.lock")
|| lower.ends_with("opennext-deploy.log"))
{
return true;
}
false
}
pub fn porcelain_line_is_xbp_ledger_noise(line: &str) -> bool {
let path = porcelain_path(line);
!path.is_empty() && is_xbp_ledger_noise_path(&path)
}
fn porcelain_path(line: &str) -> String {
let rest = if line.len() >= 3 { &line[3..] } else { line.trim() };
let rest = rest.trim();
if let Some((left, _)) = rest.split_once(" -> ") {
return unquote(left.trim());
}
unquote(rest)
}
fn unquote(s: &str) -> String {
let s = s.trim();
if s.starts_with('"') && s.ends_with('"') && s.len() >= 2 {
s[1..s.len() - 1].replace("\\\"", "\"").replace("\\\\", "\\")
} else {
s.to_string()
}
}
pub fn record_cloudflare_cli_deploy(
project_root: &Path,
app_name: &str,
provider: &str,
ok: bool,
summary: &str,
error: Option<&str>,
) {
let history_dir = project_root.join(".xbp").join("deployments");
let plan = minimal_cf_plan(project_root, app_name, provider);
let xbp_version = Some(env!("CARGO_PKG_VERSION").to_string());
let err = error.map(str::to_string);
let sanitized = record_from_plan_with_version(
&plan,
ok,
summary.to_string(),
err,
None,
xbp_version,
);
let store = DeployHistoryStore::new(&history_dir);
match store.write_record(&sanitized.record) {
Ok(path) => {
println!(
"{} deploy history {} → {}",
"·".dimmed(),
sanitized.record.id.bright_cyan(),
path.display().to_string().dimmed()
);
}
Err(e) => {
eprintln!(
"{} could not write deploy history: {e}",
"!".bright_yellow()
);
}
}
}
fn minimal_cf_plan(project_root: &Path, app_name: &str, provider: &str) -> DeployPlan {
let project = project_root
.file_name()
.and_then(|s| s.to_str())
.unwrap_or("project")
.to_string();
DeployPlan {
target: DeployTarget::Service(app_name.to_string()),
env: "production".into(),
project,
project_version: env!("CARGO_PKG_VERSION").into(),
git_sha: None,
services: vec![ServicePlan {
name: app_name.to_string(),
provider: provider.to_string(),
destination: Some("cloudflare".into()),
root_directory: None,
version: env!("CARGO_PKG_VERSION").into(),
image: None,
image_ref: None,
digest: None,
dockerfile: None,
build_context: None,
platforms: vec![],
worker_app: Some(app_name.to_string()),
rollout: None,
runtime_env: Default::default(),
container_port: None,
config_mounts: vec![],
expose: None,
deploy: ServiceDeployPlan {
namespace: None,
workload: None,
service: None,
health: vec![],
manifest_paths: vec![],
crds_path: None,
install_path: None,
selector: None,
actions: vec!["cloudflare cli deploy".into()],
},
}],
order: vec![format!("{app_name}@cloudflare")],
oci_plan: OciPlan::default(),
k8s_plan: K8sPlanView {
context: None,
default_namespace: None,
services: vec![],
},
hash: "cli-cf".into(),
}
}
pub fn run_engine_check(
project_root: &Path,
project_name: &str,
history_dir: &Path,
env: &str,
history_limit: usize,
scan_siblings: bool,
json: bool,
) -> Result<(), String> {
let limit = history_limit.max(1);
let mut reports: Vec<EngineCheckReport> = Vec::new();
reports.push(check_one_project(project_root, project_name, history_dir, env, limit)?);
if scan_siblings {
for (root, name, hdir) in discover_sibling_history_projects(project_root) {
if root == project_root {
continue;
}
match check_one_project(&root, &name, &hdir, env, limit) {
Ok(r) => reports.push(r),
Err(e) => {
eprintln!(
"{} skip {}: {e}",
"!".bright_yellow(),
root.display()
);
}
}
}
}
if json {
println!(
"{}",
serde_json::to_string_pretty(&serde_json::json!({
"engine_check": true,
"projects": reports.iter().map(|r| serde_json::json!({
"project": r.project_name,
"root": r.root,
"history_dir": r.history_dir,
"entries": r.entries,
"success": r.success,
"failed": r.failed,
"by_error_code": r.by_error_code,
"repaired": r.repaired,
})).collect::<Vec<_>>(),
}))
.map_err(|e| e.to_string())?
);
return Ok(());
}
println!();
println!(
"{} xbp deploy engine-check",
"◆".bright_magenta().bold()
);
println!(
"{}",
"repair history · classify failures · multi-repo scan (optional)".dimmed()
);
println!("{}", "─".repeat(72).bright_black());
for r in &reports {
println!();
println!(
"{} {} {}",
"▸".bright_cyan().bold(),
r.project_name.bright_white().bold(),
r.root.dimmed()
);
println!(
" {} {} entries ok={} fail={}",
"history:".bright_black(),
r.entries,
r.success.to_string().bright_green(),
r.failed.to_string().bright_red()
);
if r.repaired {
println!(
" {} rebuilt index.json from attempt files",
"repaired:".bright_green()
);
}
if !r.by_error_code.is_empty() {
println!(" {}", "by code:".bright_black());
let mut codes: Vec<_> = r.by_error_code.iter().collect();
codes.sort_by(|a, b| b.1.cmp(a.1).then_with(|| a.0.cmp(b.0)));
for (code, n) in codes.into_iter().take(10) {
println!(
" {} {}",
format!("{n:>3}").bright_white(),
code.bright_magenta()
);
}
}
if let Some(ref top) = r.latest_target {
println!(
" {} {} ({})",
"latest:".bright_black(),
top.bright_white(),
r.latest_status.as_deref().unwrap_or("?")
);
}
}
println!();
println!("{}", "─".repeat(72).bright_black());
println!("{}", "Engine policy (built-in):".bright_black());
println!(
" {} CF/OpenNext never GHCR-push; containers allow unchanged image by default",
"·".dimmed()
);
println!(
" {} OpenNext stages: install→build→entry_assert→wrangler (log: .xbp/opennext-deploy.log)",
"·".dimmed()
);
println!(
" {} strict container image: --require-new-container-image",
"·".dimmed()
);
println!(
" {} history: xbp deploy --history · repair only: --repair-history",
"·".dimmed()
);
println!();
Ok(())
}
#[derive(Debug, Clone)]
struct EngineCheckReport {
project_name: String,
root: String,
history_dir: String,
entries: usize,
success: usize,
failed: usize,
by_error_code: std::collections::BTreeMap<String, usize>,
repaired: bool,
latest_target: Option<String>,
latest_status: Option<String>,
}
fn check_one_project(
project_root: &Path,
project_name: &str,
history_dir: &Path,
env: &str,
limit: usize,
) -> Result<EngineCheckReport, String> {
let store = DeployHistoryStore::new(history_dir);
let index = store
.rebuild_index_from_records()
.map_err(|e| e.to_string())?;
let stats = store
.stats("all", env, limit.max(index.entries.len().max(1)))
.map_err(|e| e.to_string())?;
let latest = index.entries.first();
Ok(EngineCheckReport {
project_name: project_name.to_string(),
root: project_root.display().to_string(),
history_dir: history_dir.display().to_string(),
entries: stats.total,
success: stats.success,
failed: stats.failed,
by_error_code: stats.by_error_code,
repaired: true,
latest_target: latest.map(|e| e.target.clone()),
latest_status: latest.map(|e| e.status.clone()),
})
}
fn discover_sibling_history_projects(project_root: &Path) -> Vec<(std::path::PathBuf, String, std::path::PathBuf)> {
let mut out = Vec::new();
let Some(parent) = project_root.parent() else {
return out;
};
let Ok(rd) = std::fs::read_dir(parent) else {
return out;
};
for ent in rd.flatten() {
let path = ent.path();
if !path.is_dir() {
continue;
}
let deployments = path.join(".xbp").join("deployments");
if !deployments.is_dir() {
continue;
}
let name = path
.file_name()
.and_then(|s| s.to_str())
.unwrap_or("project")
.to_string();
out.push((path, name, deployments));
}
out.sort_by(|a, b| a.1.cmp(&b.1));
out
}
pub fn warn_repeated_deploy_failures(history_dir: &Path, target: &str, env: &str, min_streak: usize) {
let store = DeployHistoryStore::new(history_dir);
let Ok(entries) = store.list(target, env, min_streak.max(3)) else {
return;
};
if entries.len() < min_streak {
return;
}
let mut codes: Vec<String> = Vec::new();
for entry in &entries {
if entry.status == "success" {
break;
}
let code = entry
.error_code
.clone()
.or_else(|| {
store.load_record(entry).ok().flatten().and_then(|r| {
r.error_code.or_else(|| {
let c = classify_deploy_error(r.error.as_deref(), &r.summary, false);
Some(c.error_code.as_str().into())
})
})
})
.unwrap_or_else(|| "unknown".into());
codes.push(code);
}
if codes.len() < min_streak {
return;
}
let first = &codes[0];
if !codes.iter().take(min_streak).all(|c| c == first) {
return;
}
println!();
println!(
"{} Last {} deploys of `{}` failed with the same code `{}`.",
"!".bright_yellow().bold(),
min_streak,
target.bright_white(),
first.bright_magenta()
);
println!(
" {} fix the root cause before retrying (doctor / logs / history).",
"→".bright_cyan()
);
println!(
" {} xbp deploy --history · xbp cloudflare doctor --app {}",
"·".dimmed(),
target.dimmed()
);
println!(
" {} OpenNext log: <worker>/.xbp/opennext-deploy.log",
"·".dimmed()
);
println!();
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn detects_ledger_noise() {
assert!(is_xbp_ledger_noise_path(".xbp/deployments/foo.json"));
assert!(is_xbp_ledger_noise_path(
"C:/Users/x/repo/.xbp/deployments/2026.json"
));
assert!(is_xbp_ledger_noise_path(".xbp/last-version-scope"));
assert!(is_xbp_ledger_noise_path(
"packages/app/.xbp/opennext-deploy.log"
));
assert!(!is_xbp_ledger_noise_path("crates/cli/src/main.rs"));
assert!(!is_xbp_ledger_noise_path(".xbp/xbp.toml"));
}
#[test]
fn porcelain_noise_lines() {
assert!(porcelain_line_is_xbp_ledger_noise(
" M .xbp/deployments/x.json"
));
assert!(porcelain_line_is_xbp_ledger_noise(
"?? .xbp/releases/service-x/1.0.0.yaml"
));
assert!(!porcelain_line_is_xbp_ledger_noise(" M Cargo.toml"));
}
}