use std::process::Command; #[derive(Debug, Clone, PartialEq, Eq)] pub enum ConfigKind { NixOS, HomeManager, Colmena, } impl ConfigKind { pub fn label(&self) -> &'static str { match self { ConfigKind::NixOS => "NixOS", ConfigKind::HomeManager => "Home Manager", ConfigKind::Colmena => "Colmena", } } } #[derive(Debug, Clone)] pub struct ConfigEntry { pub name: String, pub kind: ConfigKind, } /// Discover all NixOS, Home Manager, and Colmena configurations in the flake. pub fn discover(flake_path: &str) -> Vec { let mut entries = Vec::new(); entries.extend(discover_attr(flake_path, "nixosConfigurations", ConfigKind::NixOS)); entries.extend(discover_attr(flake_path, "homeConfigurations", ConfigKind::HomeManager)); entries.extend(discover_colmena(flake_path)); entries } fn nix_attr_names(flake_path: &str, attr: &str) -> Vec { let flake_ref = format!("{}#{}", flake_path, attr); let output = Command::new("nix") .args([ "eval", "--json", &flake_ref, "--apply", "builtins.attrNames", ]) .output(); match output { Ok(out) if out.status.success() => { let json = String::from_utf8_lossy(&out.stdout); serde_json::from_str::>(&json).unwrap_or_default() } _ => vec![], } } fn discover_attr(flake_path: &str, attr: &str, kind: ConfigKind) -> Vec { nix_attr_names(flake_path, attr) .into_iter() .map(|name| ConfigEntry { name, kind: kind.clone() }) .collect() } fn discover_colmena(flake_path: &str) -> Vec { // Colmena uses `colmena` attribute; `meta` is config, not a host let flake_ref = format!("{}#colmena", flake_path); let output = Command::new("nix") .args([ "eval", "--json", &flake_ref, "--apply", r#"x: builtins.attrNames (builtins.removeAttrs x ["meta"])"#, ]) .output(); match output { Ok(out) if out.status.success() => { let json = String::from_utf8_lossy(&out.stdout); serde_json::from_str::>(&json) .unwrap_or_default() .into_iter() .map(|name| ConfigEntry { name, kind: ConfigKind::Colmena }) .collect() } _ => vec![], } }