use forge::signal::compactor;
use once_cell::sync::Lazy;
use regex::Regex;
static DOCKER_LAYER_RE: Lazy<Regex> =
Lazy::new(|| Regex::new(r"(?m)^#\d+\s+(?:CACHED|DONE \d|[^\n]*load[^\n]*)\n?").unwrap());
static WATCH_RE: Lazy<Regex> =
Lazy::new(|| Regex::new(r"(?m)^\[skaffold\] watching for changes[^\n]*\n?").unwrap());
static DIGEST_RE: Lazy<Regex> =
Lazy::new(|| Regex::new(r"(?m)^\s*digest: sha256:[a-f0-9]+\s*\n?").unwrap());
pub fn compress_build(raw: &str) -> String {
let cleaned = compactor::normalise(raw);
let s = DOCKER_LAYER_RE.replace_all(&cleaned, "");
let s = DIGEST_RE.replace_all(&s, "");
let useful: Vec<&str> = s
.lines()
.filter(|l| {
let t = l.trim();
!t.is_empty()
&& (t.contains("Building")
|| t.contains("built")
|| t.contains("pushed")
|| t.contains("tagged")
|| t.contains("Successfully")
|| t.contains("ERROR")
|| t.contains("error")
|| t.contains("WARN"))
})
.collect();
if useful.is_empty() {
return compactor::collapse_blanks(&s);
}
useful.join("\n")
}
pub fn compress_deploy(raw: &str) -> String {
let cleaned = compactor::normalise(raw);
let useful: Vec<&str> = cleaned
.lines()
.filter(|l| {
let t = l.trim();
!t.is_empty()
&& (t.contains("Deploying")
|| t.contains("deployed")
|| t.contains("service")
|| t.contains("deployment")
|| t.contains("pod")
|| t.contains("ready")
|| t.contains("Running")
|| t.contains("URL")
|| t.contains("port")
|| t.contains("ERROR")
|| t.contains("error")
|| t.contains("FAILED"))
})
.collect();
if useful.is_empty() {
return compactor::collapse_blanks(&cleaned);
}
useful.join("\n")
}
pub fn compress_dev(raw: &str) -> String {
let cleaned = compactor::normalise(raw);
let s = WATCH_RE.replace_all(&cleaned, "");
let s = DOCKER_LAYER_RE.replace_all(&s, "");
let useful: Vec<&str> = s
.lines()
.filter(|l| {
let t = l.trim();
!t.is_empty()
&& (t.contains("ERROR")
|| t.contains("error")
|| t.contains("WARN")
|| t.contains("pod")
|| t.contains("service")
|| t.contains("ready")
|| t.contains("port-forwarding")
|| t.contains("URL"))
})
.collect();
if useful.is_empty() {
return compactor::collapse_blanks(&s);
}
useful.join("\n")
}
pub fn compress_skaffold(subcmd: &str, raw: &str) -> String {
let sub = subcmd.trim();
if sub.starts_with("build") {
return compress_build(raw);
}
if sub.starts_with("deploy") || sub.starts_with("run") {
return compress_deploy(raw);
}
if sub.starts_with("dev") {
return compress_dev(raw);
}
let cleaned = compactor::normalise(raw);
let s = DOCKER_LAYER_RE.replace_all(&cleaned, "");
compactor::collapse_blanks(&s)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn build_strips_docker_layers() {
let raw = "#1 [internal] load build definition from Dockerfile\n#1 CACHED\n#2 [internal] load .dockerignore\n#2 CACHED\nBuilding [my-app]...\nSuccessfully built abc123\n";
let out = compress_build(raw);
assert!(!out.contains("#1 CACHED"), "{out}");
assert!(!out.contains("#2 CACHED"), "{out}");
assert!(
out.contains("Successfully built") || out.contains("Building"),
"{out}"
);
}
#[test]
fn deploy_keeps_service_and_pod_lines() {
let raw = "kubectl --context kind-kind apply -f /tmp/manifest.yaml\ndeployment.apps/my-app configured\nservice/my-svc unchanged\nWaiting for deployments to stabilize...\nDeployment my-app is ready.\n";
let out = compress_deploy(raw);
assert!(
out.contains("deployment") || out.contains("service") || out.contains("ready"),
"{out}"
);
}
#[test]
fn dev_strips_watch_heartbeats() {
let raw = "[skaffold] watching for changes...\n[skaffold] watching for changes...\npod/my-app-abc ready\n";
let out = compress_dev(raw);
assert!(!out.contains("watching for changes"), "{out}");
assert!(out.contains("ready"), "{out}");
}
}