liminal-cli 0.3.32

The liminal command: serve the network, maintain its stores, or serve its discovery MCP over stdio
Documentation
//! The console dist found and written as an embedded table: shared by `build.rs` and its tests.

use std::fmt::Write as _;
use std::path::{Path, PathBuf};

/// Where the console dist is read from: `console-dist/` inside the crate when a release has
/// packaged it there (a published crate cannot reach the workspace), else the workspace's
/// `apps/liminal-console/dist`.
///
/// # Errors
/// Neither holds an `index.html`: the build fails by name and says how to build the dist.
pub fn dist(manifest: &Path) -> Result<PathBuf, String> {
    let packaged = manifest.join("console-dist");
    if packaged.join("index.html").is_file() {
        return Ok(packaged);
    }
    let workspace = manifest.join("../../apps/liminal-console/dist");
    if workspace.join("index.html").is_file() {
        return Ok(workspace);
    }
    Err(format!(
        "missing console dist: neither {} nor {} holds index.html; build it with: cd apps/liminal-console && bun run build",
        packaged.display(),
        workspace.display()
    ))
}

/// The `EMBEDDED` table of every file under `dist`, sorted, with its media type, and the files
/// to watch for a rebuild.
///
/// # Errors
/// A path that cannot be read or is not Unicode.
pub fn table(dist: &Path) -> Result<(String, Vec<PathBuf>), Box<dyn std::error::Error>> {
    let mut files = Vec::new();
    collect(dist, &mut files)?;
    files.sort();
    let mut table = String::from("const EMBEDDED: &[(&str, &str, &[u8])] = &[\n");
    for path in &files {
        let relative = path
            .strip_prefix(dist)?
            .to_str()
            .ok_or("console asset path is not Unicode")?
            .replace('\\', "/");
        let absolute = std::fs::canonicalize(path)?;
        let absolute = absolute
            .to_str()
            .ok_or("console asset path is not Unicode")?;
        writeln!(
            table,
            "({:?}, {:?}, include_bytes!({absolute:?})),",
            format!("/{relative}"),
            mime(path)
        )?;
    }
    table.push_str("];\n");
    Ok((table, files))
}

fn collect(root: &Path, files: &mut Vec<PathBuf>) -> std::io::Result<()> {
    for entry in std::fs::read_dir(root)? {
        let entry = entry?;
        let kind = entry.file_type()?;
        if kind.is_dir() {
            collect(&entry.path(), files)?;
        } else if kind.is_file() {
            files.push(entry.path());
        }
    }
    Ok(())
}

fn mime(path: &Path) -> &'static str {
    match path.extension().and_then(std::ffi::OsStr::to_str) {
        Some("html") => "text/html; charset=utf-8",
        Some("js" | "mjs") => "text/javascript; charset=utf-8",
        Some("css") => "text/css; charset=utf-8",
        Some("json" | "map") => "application/json",
        Some("wasm") => "application/wasm",
        Some("svg") => "image/svg+xml",
        Some("png") => "image/png",
        Some("jpg" | "jpeg") => "image/jpeg",
        Some("ico") => "image/x-icon",
        Some("woff2") => "font/woff2",
        Some("woff") => "font/woff",
        Some("ttf") => "font/ttf",
        Some("txt") => "text/plain; charset=utf-8",
        _ => "application/octet-stream",
    }
}