Skip to main content

pharos_runtime_compiler/
component_properties.rs

1//! Installs the shared, bounded component-property renderer in built artifacts.
2use std::{fs, io, path::Path};
3
4/// Writes the shared editor/runtime engine and activates it only for authored overrides.
5pub fn compile(root: &Path) -> io::Result<()> {
6    let target = root.join("static/pharos/component-properties.js");
7    fs::create_dir_all(target.parent().ok_or(io::ErrorKind::InvalidInput)?)?;
8    fs::write(target, include_bytes!("../assets/component-properties.js"))?;
9    let metadata = root.join("static/architect-preview-overrides.json");
10    if !metadata.is_file() {
11        return Ok(());
12    }
13    if fs::metadata(&metadata)?.len() > 65536 {
14        return Err(io::ErrorKind::InvalidData.into());
15    }
16    install_in_templates(&root.join("templates"), 0)
17}
18
19/// Adds the declarative renderer tag to complete documents without modifying partials.
20fn install_in_templates(directory: &Path, depth: usize) -> io::Result<()> {
21    if !directory.exists() {
22        return Ok(());
23    }
24    if depth > 32 {
25        return Err(io::ErrorKind::InvalidData.into());
26    }
27    for entry in fs::read_dir(directory)? {
28        let entry = entry?;
29        let kind = entry.file_type()?;
30        if kind.is_symlink() {
31            return Err(io::ErrorKind::InvalidInput.into());
32        }
33        if kind.is_dir() {
34            install_in_templates(&entry.path(), depth + 1)?;
35            continue;
36        }
37        if entry
38            .path()
39            .extension()
40            .is_none_or(|extension| extension != "html")
41        {
42            continue;
43        }
44        if entry.metadata()?.len() > 8 * 1024 * 1024 {
45            return Err(io::ErrorKind::InvalidData.into());
46        }
47        let source = fs::read_to_string(entry.path())?;
48        if source.contains("data-pharos-component-properties") {
49            continue;
50        }
51        if let Some(index) = source.to_ascii_lowercase().find("</head>") {
52            let mut result = source;
53            result.insert_str(index, include_str!("../assets/component-properties.html"));
54            fs::write(entry.path(), result)?;
55        }
56    }
57    Ok(())
58}
59
60#[cfg(test)]
61mod tests {
62    use super::*;
63    /// Compilation preserves partials, installs one shared engine, and is idempotent.
64    #[test]
65    fn renderer_is_opt_in_and_idempotent() {
66        let root = std::env::temp_dir().join(format!("pharos-properties-{}", std::process::id()));
67        fs::create_dir_all(root.join("templates")).unwrap();
68        fs::create_dir_all(root.join("static")).unwrap();
69        fs::write(
70            root.join("templates/layout.html"),
71            "<head></head><body></body>",
72        )
73        .unwrap();
74        fs::write(root.join("templates/partial.html"), "<div>partial</div>").unwrap();
75        compile(&root).unwrap();
76        assert!(
77            !fs::read_to_string(root.join("templates/layout.html"))
78                .unwrap()
79                .contains("data-pharos-component-properties")
80        );
81        fs::write(root.join("static/architect-preview-overrides.json"), "{}").unwrap();
82        compile(&root).unwrap();
83        compile(&root).unwrap();
84        assert_eq!(
85            fs::read_to_string(root.join("templates/layout.html"))
86                .unwrap()
87                .matches("data-pharos-component-properties")
88                .count(),
89            1
90        );
91        assert_eq!(
92            fs::read_to_string(root.join("templates/partial.html")).unwrap(),
93            "<div>partial</div>"
94        );
95        fs::remove_dir_all(root).unwrap();
96    }
97}