ossplate 0.5.1

Scaffold and maintain one Rust-core CLI that ships cleanly through Cargo, npm, and PyPI.
use std::env;
use std::fs;
use std::path::{Path, PathBuf};

fn main() {
    let manifest_dir = PathBuf::from(env::var("CARGO_MANIFEST_DIR").expect("CARGO_MANIFEST_DIR"));
    let template_root = resolve_template_root(&manifest_dir);
    let out_dir = PathBuf::from(env::var("OUT_DIR").expect("OUT_DIR"));

    let mut entries = Vec::new();
    collect_template_entries(&template_root, &template_root, "", &mut entries);
    collect_template_entries(
        &template_root,
        &template_root,
        "core-rs/embedded-template-root/",
        &mut entries,
    );
    collect_core_entries(&manifest_dir, "core-rs/", &mut entries);
    collect_core_entries(
        &manifest_dir,
        "core-rs/embedded-template-root/core-rs/",
        &mut entries,
    );
    entries.sort_by(|a, b| a.0.cmp(&b.0));

    let mut generated =
        String::from("pub(crate) const EMBEDDED_TEMPLATE_FILES: &[(&str, &[u8])] = &[\n");
    for (target_path, absolute_path) in entries {
        let escaped_target_path = escape_rust_string(&target_path);
        let escaped_absolute_path = escape_rust_string(
            absolute_path
                .to_str()
                .expect("repo paths must be valid UTF-8 for build embedding"),
        );
        generated.push_str(&format!(
            "    (\"{escaped_target_path}\", include_bytes!(\"{escaped_absolute_path}\")),\n"
        ));
    }
    generated.push_str("];\n");

    fs::write(out_dir.join("embedded_template.rs"), generated)
        .expect("failed to write embedded template manifest");
}

fn resolve_template_root(manifest_dir: &Path) -> PathBuf {
    let generated_root = manifest_dir.join("generated-embedded-template-root");
    if generated_root.is_dir() {
        return generated_root;
    }

    if let Some(repo_root) = detect_template_repo_root(manifest_dir) {
        println!(
            "cargo:rerun-if-changed={}",
            repo_root.join("scaffold-payload.json").display()
        );
        println!(
            "cargo:rerun-if-changed={}",
            repo_root.join("source-checkout.json").display()
        );
        println!(
            "cargo:rerun-if-changed={}",
            repo_root
                .join("scripts/stage-embedded-template.mjs")
                .display()
        );
    }

    let scaffold_root = manifest_dir.join("embedded-template-root");
    if scaffold_root.is_dir() {
        return scaffold_root;
    }

    panic!(
        "missing embedded template payload. Run `node scripts/stage-distribution-assets.mjs embedded-template` from the repo root before building or packaging core-rs, or restore core-rs/embedded-template-root in a scaffolded repo."
    );
}

fn detect_template_repo_root(manifest_dir: &Path) -> Option<PathBuf> {
    let repo_root = manifest_dir.parent()?;
    let scaffold_manifest = repo_root.join("scaffold-payload.json");
    let stage_script = repo_root.join("scripts/stage-embedded-template.mjs");
    if scaffold_manifest.is_file() && stage_script.is_file() {
        Some(repo_root.to_path_buf())
    } else {
        None
    }
}

fn collect_template_entries(
    root: &Path,
    current: &Path,
    target_prefix: &str,
    entries: &mut Vec<(String, PathBuf)>,
) {
    for entry in fs::read_dir(current)
        .unwrap_or_else(|err| panic!("failed to read {}: {err}", current.display()))
    {
        let entry = entry.unwrap_or_else(|err| panic!("failed to read dir entry: {err}"));
        let path = entry.path();
        let file_type = entry
            .file_type()
            .unwrap_or_else(|err| panic!("failed to stat {}: {err}", path.display()));
        if file_type.is_dir() {
            collect_template_entries(root, &path, target_prefix, entries);
        } else if file_type.is_file() {
            println!("cargo:rerun-if-changed={}", path.display());
            let relative_path = path
                .strip_prefix(root)
                .unwrap_or_else(|err| {
                    panic!(
                        "failed to strip {} prefix from {}: {err}",
                        root.display(),
                        path.display()
                    )
                })
                .to_string_lossy()
                .replace('\\', "/");
            entries.push((format!("{target_prefix}{relative_path}"), path));
        }
    }
}

fn collect_core_entries(
    manifest_dir: &Path,
    target_prefix: &str,
    entries: &mut Vec<(String, PathBuf)>,
) {
    for relative_path in [
        "Cargo.toml",
        "Cargo.lock",
        "build.rs",
        "runtime-targets.json",
        "scaffold-payload.json",
        "source-checkout.json",
    ] {
        let path = manifest_dir.join(relative_path);
        println!("cargo:rerun-if-changed={}", path.display());
        entries.push((format!("{target_prefix}{relative_path}"), path));
    }

    let src_root = manifest_dir.join("src");
    collect_core_src_entries(&src_root, &src_root, target_prefix, entries);
}

fn collect_core_src_entries(
    root: &Path,
    current: &Path,
    target_prefix: &str,
    entries: &mut Vec<(String, PathBuf)>,
) {
    for entry in fs::read_dir(current)
        .unwrap_or_else(|err| panic!("failed to read {}: {err}", current.display()))
    {
        let entry = entry.unwrap_or_else(|err| panic!("failed to read dir entry: {err}"));
        let path = entry.path();
        let file_type = entry
            .file_type()
            .unwrap_or_else(|err| panic!("failed to stat {}: {err}", path.display()));
        if file_type.is_dir() {
            collect_core_src_entries(root, &path, target_prefix, entries);
        } else if file_type.is_file() {
            println!("cargo:rerun-if-changed={}", path.display());
            let relative_path = path
                .strip_prefix(root)
                .unwrap_or_else(|err| {
                    panic!(
                        "failed to strip {} prefix from {}: {err}",
                        root.display(),
                        path.display()
                    )
                })
                .to_string_lossy()
                .replace('\\', "/");
            entries.push((format!("{target_prefix}src/{relative_path}"), path));
        }
    }
}

fn escape_rust_string(value: &str) -> String {
    value.chars().flat_map(char::escape_default).collect()
}