use forge::signal::compactor;
use once_cell::sync::Lazy;
use regex::Regex;
static CACHE_HIT_RE: Lazy<Regex> =
Lazy::new(|| Regex::new(r"(?m)^\s+✔\s+nx run\s+(\S+)\s+\[existing outputs[^\]]*\]").unwrap());
static SUCCESS_RE: Lazy<Regex> =
Lazy::new(|| Regex::new(r"(?m)^\s+✔\s+nx run\s+(\S+)\s+\(([^)]+)\)").unwrap());
static FAIL_RE: Lazy<Regex> = Lazy::new(|| Regex::new(r"(?m)^\s+✖\s+nx run\s+(\S+)").unwrap());
static HR_RE: Lazy<Regex> = Lazy::new(|| Regex::new(r"(?m)^\s+[─—]{10,}\s*$").unwrap());
static CACHE_EXPLAIN_RE: Lazy<Regex> =
Lazy::new(|| Regex::new(r"(?m)^\s+Nx (read|restored|will use|skipped)[^\n]*\n?").unwrap());
pub fn compress_nx(raw: &str) -> String {
let cleaned = compactor::normalise(raw);
let s = CACHE_HIT_RE.replace_all(&cleaned, " ✔ $1 (cached)");
let s = SUCCESS_RE.replace_all(&s, " ✔ $1 ($2)");
let s = FAIL_RE.replace_all(&s, " ✖ $1");
let s = HR_RE.replace_all(&s, "");
let s = CACHE_EXPLAIN_RE.replace_all(&s, "");
let out: Vec<&str> = s
.lines()
.filter(|l| {
let t = l.trim();
!(t.starts_with("> NX") && t.contains("Running target"))
})
.collect();
compactor::collapse_blanks(&out.join("\n"))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn collapses_cache_hit_lines() {
let raw = " ✔ nx run app:build [existing outputs match the cache, left as is]\n ✔ nx run docs:build (2s)\n";
let out = compress_nx(raw);
assert!(out.contains("app:build (cached)"), "{out}");
assert!(out.contains("docs:build (2s)"), "{out}");
assert!(!out.contains("existing outputs match"), "{out}");
}
#[test]
fn strips_horizontal_rules() {
let raw = " ✔ nx run app:build (1s)\n\n ────────────────────────────────────────\n\n > NX Successfully ran target build\n";
let out = compress_nx(raw);
assert!(!out.contains("────"), "{out}");
assert!(out.contains("Successfully ran target"), "{out}");
}
#[test]
fn strips_verbose_cache_explanation() {
let raw = " ✔ nx run app:build (1s)\n Nx read the output from the cache instead of running the command for 1 out of 3 tasks.\n\n > NX Successfully ran\n";
let out = compress_nx(raw);
assert!(!out.contains("Nx read the output from the cache"), "{out}");
assert!(out.contains("app:build"), "{out}");
}
#[test]
fn keeps_failure_output() {
let raw = " ✖ nx run app:build\n\n > app@0.0.1 build\n > tsc --noEmit\n\n error TS2304: Cannot find name 'foo'.\n\n ✖ 1/3 targets failed\n";
let out = compress_nx(raw);
assert!(out.contains("✖") && out.contains("app:build"), "{out}");
assert!(out.contains("error TS2304"), "{out}");
assert!(out.contains("1/3"), "{out}");
}
#[test]
fn strips_running_target_banner() {
let raw = "\n > NX Running target build for 3 projects:\n\n - app\n - docs\n";
let out = compress_nx(raw);
assert!(!out.contains("Running target build for"), "{out}");
}
}