bctx-weave 0.1.29

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

// "Vercel CLI 33.4.0" version banner
static VERSION_RE: Lazy<Regex> =
    Lazy::new(|| Regex::new(r"(?m)^Vercel CLI \d+\.\d+\.\d+[^\n]*\n?").unwrap());
// Upload progress: "Uploading [====] 100%" or similar
static UPLOAD_PROGRESS_RE: Lazy<Regex> =
    Lazy::new(|| Regex::new(r"(?m)^[^\n]*\[=+[^\]]*\][^\n]*\n?").unwrap());
// "Queued" / "Building" / "Checking" interim status lines (without URL)
static INTERIM_RE: Lazy<Regex> = Lazy::new(|| {
    Regex::new(r"(?m)^[^\n]*(Queued|Queuing|Initializing|Cloning repo)[^\n]*\n?").unwrap()
});
// Spinner / progress indicator lines starting with special chars
static SPINNER_RE: Lazy<Regex> =
    Lazy::new(|| Regex::new(r"(?m)^\s*[⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏][^\n]*\n?").unwrap());

// ── vercel deploy ─────────────────────────────────────────────────────────────

pub fn compress_deploy(raw: &str) -> String {
    let cleaned = compactor::normalise(raw);
    let s = VERSION_RE.replace_all(&cleaned, "");
    let s = UPLOAD_PROGRESS_RE.replace_all(&s, "");
    let s = INTERIM_RE.replace_all(&s, "");
    let s = SPINNER_RE.replace_all(&s, "");

    let useful: Vec<&str> = s
        .lines()
        .filter(|l| {
            let t = l.trim();
            !t.is_empty()
                && (t.contains("vercel.app")
                    || t.contains(".vercel.app")
                    || t.contains("Production:")
                    || t.contains("Preview:")
                    || t.contains("Inspect:")
                    || t.contains("")
                    || t.contains("")
                    || t.contains("Deployed")
                    || t.contains("deployed")
                    || t.contains("Deployment complete")
                    || t.contains("Build Completed")
                    || t.contains("Duration:")
                    || t.contains("error")
                    || t.contains("Error")
                    || t.contains("Warning")
                    || t.starts_with("Error:"))
        })
        .collect();

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

// ── vercel build (local build output) ────────────────────────────────────────

pub fn compress_build(raw: &str) -> String {
    let cleaned = compactor::normalise(raw);
    let s = VERSION_RE.replace_all(&cleaned, "");
    // Keep build output lines (errors, warnings, output sizes, duration)
    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("Built")
                    || t.contains("Build Completed")
                    || t.contains("Route")
                    || t.contains("Page")
                    || t.contains("First Load")
                    || t.contains("chunks")
                    || t.contains("Duration")
                    || t.contains("")
                    || t.contains("λ")
                    || t.contains(""))
        })
        .collect();
    if useful.is_empty() {
        return compactor::collapse_blanks(&s);
    }
    useful.join("\n")
}

// ── vercel env / domains / teams ─────────────────────────────────────────────

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

// ── vercel 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")
}

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

pub fn compress_vercel(subcmd: &str, raw: &str) -> String {
    let sub = subcmd.trim();
    if sub.is_empty() || sub.starts_with("deploy") || sub.starts_with("--prod") {
        return compress_deploy(raw);
    }
    if sub.starts_with("build") {
        return compress_build(raw);
    }
    if sub.starts_with("logs") {
        return compress_logs(raw);
    }
    if sub.starts_with("env")
        || sub.starts_with("domains")
        || sub.starts_with("teams")
        || sub.starts_with("projects")
        || sub.starts_with("ls")
        || sub.starts_with("list")
    {
        return compress_list(raw);
    }
    let cleaned = compactor::normalise(raw);
    let s = VERSION_RE.replace_all(&cleaned, "");
    compactor::collapse_blanks(&s)
}

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

    #[test]
    fn deploy_keeps_preview_url() {
        let raw = "Vercel CLI 33.4.0\nDeploying my-app...\nQueued\nBuilding\n✅  Build Completed in /vercel/output [42s]\n⠸  Deploying...\nProduction: https://my-app.vercel.app [copied to clipboard]\n";
        let out = compress_deploy(raw);
        assert!(out.contains("vercel.app"), "{out}");
        assert!(out.contains("Build Completed"), "{out}");
        assert!(!out.contains("Vercel CLI 33"), "{out}");
        assert!(!out.contains("Queued"), "{out}");
    }

    #[test]
    fn deploy_keeps_error_lines() {
        let raw = "Vercel CLI 33.4.0\nQueued\nBuilding\nError: Build failed\n  ./src/index.ts(10): error TS2304: Cannot find name 'foo'.\n";
        let out = compress_deploy(raw);
        assert!(out.contains("Error: Build failed"), "{out}");
        assert!(out.contains("TS2304"), "{out}");
        assert!(!out.contains("Queued"), "{out}");
    }

    #[test]
    fn deploy_strips_upload_progress() {
        let raw = "Uploading [========>  ] 85%\nUploading [==========] 100%\nDeployment complete\nhttps://my-app-abc123.vercel.app\n";
        let out = compress_deploy(raw);
        assert!(!out.contains("========"), "{out}");
        assert!(out.contains("vercel.app"), "{out}");
    }

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