bctx-weave 0.1.25

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

// "transforming (1234) src/component.tsx"
static VITE_TRANSFORM_RE: Lazy<Regex> =
    Lazy::new(|| Regex::new(r"(?m)^transforming \(\d+\)[^\n]*\n?").unwrap());
// "✓ 234 modules transformed."
#[allow(dead_code)]
static VITE_MODULES_RE: Lazy<Regex> =
    Lazy::new(|| Regex::new(r"(?m)^[✓✗] \d+ modules transformed\.\n?").unwrap());
// Next.js: "Creating an optimized production build..."
static NEXT_CREATING_RE: Lazy<Regex> =
    Lazy::new(|| Regex::new(r"(?m)^Creating an optimized production build[^\n]*\n?").unwrap());
// Next.js route table lines that just show loading spinner: "   ○ /_not-found" etc.
// Keep them — they're useful. Strip only the spinner progress lines.
static NEXT_SPINNER_RE: Lazy<Regex> =
    Lazy::new(|| Regex::new(r"(?m)^\s+[⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏][^\n]*\n?").unwrap());
// Webpack/Rollup chunk progress: "[==========] 100%" style
static PROGRESS_BAR_RE: Lazy<Regex> =
    Lazy::new(|| Regex::new(r"(?m)^\[=+>?=*\] \d+%[^\n]*\n?").unwrap());
// esbuild: verbose "  entry: src/index.ts"
static ESBUILD_ENTRY_RE: Lazy<Regex> =
    Lazy::new(|| Regex::new(r"(?m)^\s+entry:[^\n]+\n?").unwrap());

// ── vite build ────────────────────────────────────────────────────────────────

pub fn compress_vite_build(raw: &str) -> String {
    let cleaned = compactor::normalise(raw);
    let s = VITE_TRANSFORM_RE.replace_all(&cleaned, "");
    let s = PROGRESS_BAR_RE.replace_all(&s, "");
    compactor::collapse_blanks(&s)
}

// ── next build ────────────────────────────────────────────────────────────────

pub fn compress_next_build(raw: &str, exit_code: i32) -> String {
    let cleaned = compactor::normalise(raw);
    let s = NEXT_CREATING_RE.replace_all(&cleaned, "");
    let s = NEXT_SPINNER_RE.replace_all(&s, "");
    let s = PROGRESS_BAR_RE.replace_all(&s, "");

    if exit_code == 0 {
        // Keep: route table, size info, "Ready in Xs"
        let useful: Vec<&str> = s
            .lines()
            .filter(|l| {
                let t = l.trim();
                !t.is_empty()
                    && (l.starts_with("Route")
                        || l.starts_with("   ")
                        || t.starts_with("First Load")
                        || t.contains("kB")
                        || t.starts_with("Ready")
                        || t.starts_with("info")
                        || t.contains("Compiled")
                        || t.contains("error")
                        || t.contains("warn"))
            })
            .collect();
        if !useful.is_empty() {
            return useful.join("\n");
        }
    }

    compactor::collapse_blanks(&s)
}

// ── esbuild ───────────────────────────────────────────────────────────────────

pub fn compress_esbuild(raw: &str) -> String {
    let cleaned = compactor::normalise(raw);
    let s = ESBUILD_ENTRY_RE.replace_all(&cleaned, "");
    let s = PROGRESS_BAR_RE.replace_all(&s, "");
    compactor::collapse_blanks(&s)
}

// ── turbopack / turborepo ──────────────────────────────────────────────────────

pub fn compress_turbo(raw: &str) -> String {
    let cleaned = compactor::normalise(raw);
    // Strip "• Packages in scope:", "• Running build in N packages" type noise
    let noise: &[&str] = &["• Packages in scope", "• Running ", ">>> Finished"];
    let lines: Vec<&str> = cleaned
        .lines()
        .filter(|l| {
            let t = l.trim();
            !t.is_empty() && !noise.iter().any(|n| t.starts_with(n))
        })
        .collect();
    if lines.len() <= 50 {
        return lines.join("\n");
    }
    format!(
        "{}\n... [{} more lines]",
        lines[..50].join("\n"),
        lines.len() - 50
    )
}

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

pub fn compress_vite(subcmd: &str, raw: &str, _exit_code: i32) -> String {
    match subcmd.trim() {
        "build" => compress_vite_build(raw),
        _ => compactor::normalise(raw),
    }
}

pub fn compress_next(subcmd: &str, raw: &str, exit_code: i32) -> String {
    match subcmd.trim() {
        "build" => compress_next_build(raw, exit_code),
        _ => compactor::normalise(raw),
    }
}

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

    #[test]
    fn vite_build_strips_transform_progress() {
        let raw = "vite v5.0.0 building for production...\ntransforming (1) src/main.ts\ntransforming (234) src/App.tsx\ntransforming (512) src/utils.ts\n✓ 512 modules transformed.\ndist/index.html  0.46 kB\ndist/assets/index-abc.js  142.3 kB\n";
        let out = compress_vite_build(raw);
        assert!(!out.contains("transforming (1)"), "{out}");
        assert!(!out.contains("transforming (234)"), "{out}");
        assert!(out.contains("dist/"), "{out}");
    }

    #[test]
    fn next_build_success_keeps_route_table() {
        let raw = "Creating an optimized production build...\nCompiled successfully\n\nRoute (app)               Size     First Load JS\n   ○ /                   142 B        87.5 kB\n   ○ /about              200 B        87.6 kB\n\nReady in 4.2s\n";
        let out = compress_next_build(raw, 0);
        assert!(!out.contains("Creating an optimized"), "{out}");
        assert!(out.contains("Route") || out.contains("Ready"), "{out}");
    }

    #[test]
    fn esbuild_strips_entry_verbose() {
        let raw = "  entry: src/index.ts\n  entry: src/worker.ts\n\n  dist/index.js   142kb\n  dist/worker.js   12kb\n";
        let out = compress_esbuild(raw);
        assert!(!out.contains("entry: src/"), "{out}");
        assert!(out.contains("dist/"), "{out}");
    }
}