greentic-pack-dev 1.1.26387674049

Greentic pack builder CLI
Documentation
use crate::manifest::PackSpec;
use crate::path_safety::normalize_under_root;
use anyhow::{Context, Result};
use sha2::{Digest, Sha256};
use std::collections::BTreeSet;
use std::fs;
use std::path::{Path, PathBuf};
use walkdir::WalkDir;

#[derive(Debug, Clone)]
pub struct TemplateAsset {
    pub logical_path: String,
    #[allow(dead_code)]
    pub absolute_path: PathBuf,
    pub bytes: Vec<u8>,
    pub sha256: String,
    pub size: u64,
}

pub fn collect_templates(pack_dir: &Path, spec: &PackSpec) -> Result<Vec<TemplateAsset>> {
    let mut assets = Vec::new();
    let mut seen_paths = BTreeSet::new();

    for dir in &spec.template_dirs {
        let relative_dir = PathBuf::from(dir);
        let absolute_dir = normalize_under_root(pack_dir, &relative_dir)?;

        if !absolute_dir.exists() {
            tracing::warn!("template directory missing: {}", absolute_dir.display());
            continue;
        }

        for entry in WalkDir::new(&absolute_dir)
            .into_iter()
            .filter_map(|e| e.ok())
        {
            if !entry.file_type().is_file() {
                continue;
            }

            let path = entry.into_path();
            let rel_to_root = path
                .strip_prefix(&absolute_dir)
                .unwrap_or(path.as_path())
                .to_path_buf();
            let logical_path = relative_dir
                .join(&rel_to_root)
                .components()
                .map(|comp| comp.as_os_str().to_string_lossy())
                .collect::<Vec<_>>()
                .join("/");

            if !seen_paths.insert(logical_path.clone()) {
                anyhow::bail!("duplicate template asset detected: {}", logical_path);
            }

            let bytes = fs::read(&path)
                .with_context(|| format!("failed to read template {}", path.display()))?;
            let digest = Sha256::digest(&bytes);
            let sha256 = hex::encode(digest);
            let size = bytes.len() as u64;

            assets.push(TemplateAsset {
                logical_path,
                absolute_path: path,
                bytes,
                sha256,
                size,
            });
        }
    }

    assets.sort_by(|a, b| a.logical_path.cmp(&b.logical_path));
    Ok(assets)
}