poet 0.7.0

Static site generator with optional MCP server and LLM SEO optimizations
Documentation
use std::path::PathBuf;
use std::str::FromStr as _;
use std::sync::Arc;

use anyhow::Result;
use anyhow::anyhow;
use esbuild_metafile::EsbuildMetaFile;
use log::warn;

use crate::filesystem::Filesystem as _;
use crate::filesystem::read_file_contents_result::ReadFileContentsResult;
use crate::filesystem::storage::Storage;

pub async fn read_esbuild_metafile_or_default(
    source_filesystem: Arc<Storage>,
) -> Result<Arc<EsbuildMetaFile>> {
    Ok(match source_filesystem
        .read_file_contents(&PathBuf::from("esbuild-meta.json"))
        .await?
    {
        ReadFileContentsResult::Directory => {
            return Err(anyhow!(
                "esbuild metafile should be a file, not a directory"
            ));
        }
        ReadFileContentsResult::Found { contents } => EsbuildMetaFile::from_str(&contents)?,
        ReadFileContentsResult::NotFound => {
            warn!("esbuild metafile not found, proceeding without it");

            EsbuildMetaFile::default()
        }
    }
    .into())
}

#[cfg(test)]
mod tests {
    use indoc::indoc;
    use tempfile::tempdir;

    use super::*;
    use crate::asset_manager::AssetManager;
    use crate::asset_path_renderer::AssetPathRenderer;

    const METAFILE: &str = indoc! {r#"
        {
            "outputs": {
                "static/logo_ABCDEF12.png": {
                    "imports": [],
                    "inputs": { "logo.png": {} }
                }
            }
        }
    "#};

    fn asset_manager(metafile: Arc<EsbuildMetaFile>) -> AssetManager {
        AssetManager::from_esbuild_metafile(
            metafile,
            AssetPathRenderer {
                base_path: "/".to_string(),
            },
        )
    }

    #[tokio::test]
    async fn reads_and_parses_an_existing_metafile() -> Result<()> {
        let directory = tempdir()?;
        let storage = Storage {
            base_directory: directory.path().to_path_buf(),
        };

        storage
            .set_file_contents(&PathBuf::from("esbuild-meta.json"), METAFILE)
            .await?;

        let metafile = read_esbuild_metafile_or_default(Arc::new(storage)).await?;

        assert_eq!(
            asset_manager(metafile).file("logo.png"),
            Ok("/static/logo_ABCDEF12.png".to_string())
        );

        Ok(())
    }

    #[tokio::test]
    async fn falls_back_to_default_when_metafile_is_missing() -> Result<()> {
        let directory = tempdir()?;
        let storage = Storage {
            base_directory: directory.path().to_path_buf(),
        };

        let metafile = read_esbuild_metafile_or_default(Arc::new(storage)).await?;

        assert!(asset_manager(metafile).file("logo.png").is_err());

        Ok(())
    }

    #[tokio::test]
    async fn errors_when_metafile_path_is_a_directory() -> Result<()> {
        let directory = tempdir()?;
        let storage = Storage {
            base_directory: directory.path().to_path_buf(),
        };

        std::fs::create_dir(directory.path().join("esbuild-meta.json"))?;

        assert!(
            read_esbuild_metafile_or_default(Arc::new(storage))
                .await
                .is_err()
        );

        Ok(())
    }
}