noxid-cli 0.2.0

The Noxid compiler command line: check, build, test, adapt, and the agent surface
use std::fs;
use std::path::PathBuf;
use std::process::{Command, Output};
use std::sync::atomic::{AtomicU64, Ordering};

static NEXT_FIXTURE: AtomicU64 = AtomicU64::new(0);

struct Fixture {
    root: PathBuf,
}

impl Fixture {
    fn new() -> Self {
        let root = std::env::temp_dir().join(format!(
            "noxid-wo29-devtools-privacy-{}-{}",
            std::process::id(),
            NEXT_FIXTURE.fetch_add(1, Ordering::Relaxed)
        ));
        fs::create_dir_all(root.join("src/routes")).expect("create DevTools fixture");
        fs::write(
            root.join("Noxid.toml"),
            "[app]\ntitle = \"Metadata privacy\"\nbase = \"/shop\"\n",
        )
        .expect("write manifest");
        fs::write(
            root.join("src/routes/+page.nox"),
            "component Home { state { count: Int = 0 } route { title: \"Home\" } view { <main>{count}</main> } }\n",
        )
        .expect("write route");
        Self { root }
    }

    fn build(&self, out_dir: &str) -> Output {
        let mut command = Command::new(env!("CARGO_BIN_EXE_noxid"));
        command.args(["build", ".", "--out-dir", out_dir]);
        command
            .current_dir(&self.root)
            .output()
            .expect("build DevTools privacy fixture")
    }
}

impl Drop for Fixture {
    fn drop(&mut self) {
        let _ = fs::remove_dir_all(&self.root);
    }
}

fn assert_success(output: &Output, context: &str) {
    assert!(
        output.status.success(),
        "{context}\nstdout:\n{}\nstderr:\n{}",
        String::from_utf8_lossy(&output.stdout),
        String::from_utf8_lossy(&output.stderr)
    );
}

#[test]
fn production_devtools_metadata_contains_only_project_relative_source_paths() {
    let fixture = Fixture::new();
    let production = fixture.build("dist");
    assert_success(&production, "build production metadata fixture");
    let metadata = fs::read_to_string(fixture.root.join("dist/app.devtools.json"))
        .expect("read production DevTools metadata");
    let absolute_root = fixture.root.to_string_lossy();
    assert!(
        !metadata.contains(absolute_root.as_ref()),
        "production metadata leaked project root {absolute_root}: {metadata}"
    );
    assert!(metadata.contains("\"source\": \".\""), "{metadata}");
    assert!(
        metadata.contains("\"source\":\"src/routes/+page.nox\""),
        "{metadata}"
    );
}