mini-static 0.17.0

A secure, async static file server with streaming, traversal protection, and connection limits.
Documentation
use mini_static::{CssOptions, CssTool, JsOptions, JsTool, Server};
use std::env;
use std::path::Path;
use std::time::Duration;

#[tokio::main(flavor = "current_thread")]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let port = env::var("PORT")
        .unwrap_or_else(|_| "8080".to_string())
        .parse::<u16>()?;

    let root = env::var("ROOT").unwrap_or_else(|_| "./public".to_string());
    let root_path = Path::new(&root);

    // `with_immutable_assets` opts fingerprinted filenames (`vendor.a1b2c3.js`) into a
    // year-long, cacheable-forever `Cache-Control`, since a content change would produce
    // a new filename rather than mutating this one. Everything else keeps the default
    // `no-cache` (see `Server::with_immutable_assets`).
    let server = Server::new(root_path)?.with_immutable_assets(|path| {
        path.file_name()
            .and_then(|name| name.to_str())
            .is_some_and(|name| name.contains(".a1b2c3."))
    });

    // CSS/JS bundling demo (see `Server::with_css_tool`/`Server::with_js_tool`): both
    // are opt-in and delegate to an external CLI tool mini-static does not install —
    // `lightningcss` and `esbuild` must already be on `PATH`. Bundles every `.css`
    // under `css-src/` (with `@import` resolution) into `public/style-bundle.css`, and
    // bundles `js-src/main.js`'s module graph into `public/bundle.js`.
    let css_src = Path::new("./css-src");
    let js_src = Path::new("./js-src");

    let mut server = server;

    let has_css_bundle = if css_src.exists() {
        match server.clone().with_source_folder(css_src) {
            Ok(s) => {
                server = s.with_css_tool(
                    CssTool::LightningCss,
                    CssOptions::new()
                        .bundle(true)
                        .minify(true)
                        .bundle_output_name("style-bundle.css"),
                );
                true
            }
            Err(e) => {
                eprintln!("warning: CSS bundling configuration failed: {}", e);
                false
            }
        }
    } else {
        false
    };

    let has_js_bundle = if js_src.join("main.js").exists() {
        match server.clone().with_source_folder(js_src) {
            Ok(s) => match s.with_js_tool(
                JsTool::Esbuild,
                JsOptions::new()
                    .bundle_entry(&js_src.join("main.js"), "bundle.js")
                    .minify(true),
            ) {
                Ok(s) => {
                    server = s;
                    true
                }
                Err(e) => {
                    eprintln!("warning: JS bundling configuration failed: {}", e);
                    false
                }
            },
            Err(e) => {
                eprintln!("warning: JS bundling configuration failed: {}", e);
                false
            }
        }
    } else {
        false
    };

    // Live-reload (background file watcher, SSE stream, injected reload script — see
    // `Server::with_live_reload`) is only enabled in debug builds, matching the
    // convention `mini-unified`'s `add_reload_route` uses: a release build never pays
    // for the watcher or ships the injected script.
    #[cfg(debug_assertions)]
    {
        server = server.with_live_reload();
    }

    // Bind to all interfaces (0.0.0.0) so the server is accessible from outside
    // (e.g., from the host when running in Docker)
    let (_port, handle) = server.run_all(port, Duration::from_secs(30)).await?;

    println!("mini-static listening on 0.0.0.0:{}", port);
    println!("serving files from: {}", root);
    println!("immutable caching enabled for *.a1b2c3.* (see vendor.a1b2c3.js)");
    println!("precompressed sidecar demo: bundle.js / bundle.js.gz");
    if has_css_bundle {
        println!(
            "css bundling enabled (lightningcss): {} -> {}",
            css_src.display(),
            root_path.join("style-bundle.css").display()
        );
    }
    if has_js_bundle {
        println!(
            "js bundling enabled (esbuild): {} -> {}",
            js_src.join("main.js").display(),
            root_path.join("bundle.js").display()
        );
    }
    #[cfg(debug_assertions)]
    println!("live-reload enabled at {}", mini_static::LIVE_RELOAD_PATH);

    // Keep the server running until interrupted
    tokio::signal::ctrl_c().await?;
    println!("shutting down...");
    handle.shutdown().await;

    Ok(())
}