bctx-weave 0.1.11

bctx-weave — FilterMesh lens pipeline, CLI interception, domain compression
Documentation
use forge::signal::compactor;
use once_cell::sync::Lazy;
use regex::Regex;

// Streaming build log lines: "#1 [internal] load build definition from Dockerfile"
static BUILD_STEP_RE: Lazy<Regex> =
    Lazy::new(|| Regex::new(r"(?m)^#\d+ \[(?:internal|[^\]]+)\][^\n]*\n?").unwrap());
// Docker layer cache: "#1 CACHED" or "#3 sha256:..."
static LAYER_CACHE_RE: Lazy<Regex> =
    Lazy::new(|| Regex::new(r"(?m)^#\d+ (?:CACHED|sha256:)[^\n]*\n?").unwrap());
// "==> Monitoring deployment" and progress dots
static MONITORING_RE: Lazy<Regex> =
    Lazy::new(|| Regex::new(r"(?m)^==> Monitoring deployment[^\n]*\n?").unwrap());
// Allocation status relay: " 1 desired, 1 placed, 1 healthy, 0 unhealthy [health checks: 1 total, 1 passing]"
static ALLOC_STATUS_RE: Lazy<Regex> =
    Lazy::new(|| Regex::new(r"(?m)^\s+\d+ desired[^\n]*\n?").unwrap());
// "Waiting for..." interim lines
static WAITING_RE: Lazy<Regex> =
    Lazy::new(|| Regex::new(r"(?m)^[^\n]*Waiting for[^\n]*\n?").unwrap());

// ── fly deploy ────────────────────────────────────────────────────────────────

pub fn compress_deploy(raw: &str) -> String {
    let cleaned = compactor::normalise(raw);
    let s = BUILD_STEP_RE.replace_all(&cleaned, "");
    let s = LAYER_CACHE_RE.replace_all(&s, "");
    let s = MONITORING_RE.replace_all(&s, "");
    let s = ALLOC_STATUS_RE.replace_all(&s, "");
    let s = WAITING_RE.replace_all(&s, "");

    let useful: Vec<&str> = s
        .lines()
        .filter(|l| {
            let t = l.trim();
            !t.is_empty()
                && (t.contains("fly.dev")
                    || t.contains(".fly.io")
                    || t.contains("Deployed")
                    || t.contains("deployed")
                    || t.contains("Release")
                    || t.contains("Machine")
                    || t.contains("healthy")
                    || t.contains("Hostname:")
                    || t.contains("image:")
                    || t.contains("image digest:")
                    || t.contains("==>")
                    || t.contains("error")
                    || t.contains("Error")
                    || t.contains("FAILED")
                    || t.contains("failed"))
        })
        .collect();

    if useful.is_empty() {
        return compactor::collapse_blanks(&s);
    }
    useful.join("\n")
}

// ── fly launch ────────────────────────────────────────────────────────────────

pub fn compress_launch(raw: &str) -> String {
    let cleaned = compactor::normalise(raw);
    // Keep configuration prompts and results, strip verbose scanning
    let lines: Vec<&str> = cleaned
        .lines()
        .filter(|l| {
            let t = l.trim();
            !t.is_empty()
                && !t.starts_with("Scanning")
                && !t.starts_with("Detected")
                && (t.contains("Created")
                    || t.contains("fly.toml")
                    || t.contains("fly.dev")
                    || t.contains("Hostname")
                    || t.contains("Region")
                    || t.contains("org:")
                    || t.contains("Your app")
                    || t.contains("error")
                    || t.contains("==>"))
        })
        .collect();
    if lines.is_empty() {
        return compactor::collapse_blanks(&cleaned);
    }
    lines.join("\n")
}

// ── fly logs ──────────────────────────────────────────────────────────────────

pub fn compress_logs(raw: &str) -> String {
    let cleaned = compactor::normalise(raw);
    let lines: Vec<&str> = cleaned.lines().filter(|l| !l.trim().is_empty()).collect();
    if lines.len() > 50 {
        return format!(
            "{}\n… [{} more log lines] …",
            lines[..50].join("\n"),
            lines.len() - 50
        );
    }
    lines.join("\n")
}

// ── fly status ────────────────────────────────────────────────────────────────

pub fn compress_status(raw: &str) -> String {
    let cleaned = compactor::normalise(raw);
    let lines: Vec<&str> = cleaned.lines().filter(|l| !l.trim().is_empty()).collect();
    if lines.len() > 25 {
        return format!(
            "{}\n… [{} more lines] …",
            lines[..25].join("\n"),
            lines.len() - 25
        );
    }
    cleaned
}

// ── fly machines list ─────────────────────────────────────────────────────────

pub fn compress_machines(raw: &str) -> String {
    let cleaned = compactor::normalise(raw);
    let lines: Vec<&str> = cleaned.lines().collect();
    if lines.len() > 30 {
        return format!(
            "{}\n… [{} more machines] …",
            lines[..30].join("\n"),
            lines.len() - 30
        );
    }
    cleaned
}

// ── top-level dispatcher ──────────────────────────────────────────────────────

pub fn compress_fly(subcmd: &str, raw: &str) -> String {
    let sub = subcmd.trim();
    if sub.starts_with("deploy") {
        return compress_deploy(raw);
    }
    if sub.starts_with("launch") {
        return compress_launch(raw);
    }
    if sub.starts_with("logs") {
        return compress_logs(raw);
    }
    if sub.starts_with("status") {
        return compress_status(raw);
    }
    if sub.starts_with("machine") || sub.starts_with("machines") {
        return compress_machines(raw);
    }
    // scale, secrets, certs, ips, volumes, etc.
    compactor::normalise(raw)
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn deploy_strips_build_step_noise() {
        let raw = "#1 [internal] load build definition from Dockerfile\n#1 CACHED\n#2 [1/3] FROM docker.io/library/node:18\n==> Building image\n==> image: registry.fly.io/my-app:abc123\n==> Deploying\nhttps://my-app.fly.dev\n";
        let out = compress_deploy(raw);
        assert!(!out.contains("#1 [internal]"), "{out}");
        assert!(!out.contains("CACHED"), "{out}");
        assert!(out.contains("fly.dev"), "{out}");
        assert!(out.contains("image:"), "{out}");
    }

    #[test]
    fn deploy_strips_monitoring_and_waiting() {
        let raw = "==> Monitoring deployment\nWaiting for machines to be healthy...\n 1 desired, 1 placed, 1 healthy, 0 unhealthy\nhttps://my-app.fly.dev deployed\n";
        let out = compress_deploy(raw);
        assert!(!out.contains("Monitoring deployment"), "{out}");
        assert!(!out.contains("Waiting for"), "{out}");
        assert!(!out.contains("desired,"), "{out}");
        assert!(out.contains("deployed"), "{out}");
    }

    #[test]
    fn logs_truncates_long_stream() {
        let lines: Vec<String> = (0..60)
            .map(|i| format!("2024-01-01T00:00:{i:02}Z app[abc] log line {i}"))
            .collect();
        let out = compress_logs(&lines.join("\n"));
        assert!(out.contains("more log lines"), "{out}");
    }

    #[test]
    fn status_truncates_many_machines() {
        let lines: Vec<String> = (0..30)
            .map(|i| format!("machine-{i:04}  started  cdg  shared-cpu-1x"))
            .collect();
        let out = compress_status(&lines.join("\n"));
        assert!(out.contains("more lines"), "{out}");
    }
}