1use cuengine::ModuleEvalOptions;
2use cuenv_core::ModuleEvaluation;
3use cuenv_core::Result;
4use cuenv_core::manifest::Project;
5use std::path::{Path, PathBuf};
6
7#[derive(Debug, Clone)]
8pub struct DiscoveredCIProject {
9 pub path: PathBuf,
10 pub config: Project,
11}
12
13fn find_cue_module_root(start: &Path) -> Option<PathBuf> {
15 let mut current = start.canonicalize().ok()?;
16 loop {
17 if current.join("cue.mod").is_dir() {
18 return Some(current);
19 }
20 if !current.pop() {
21 return None;
22 }
23 }
24}
25
26pub fn discover_projects() -> Result<Vec<DiscoveredCIProject>> {
34 let cwd = std::env::current_dir().map_err(|e| cuenv_core::Error::Io {
36 source: e,
37 path: None,
38 operation: "get current directory".to_string(),
39 })?;
40
41 let module_root = find_cue_module_root(&cwd).ok_or_else(|| {
42 cuenv_core::Error::configuration(
43 "Not inside a CUE module. Run 'cue mod init' or navigate to a directory with cue.mod/",
44 )
45 })?;
46
47 let options = ModuleEvalOptions {
49 recursive: true,
50 ..Default::default()
51 };
52 let raw_result = cuengine::evaluate_module(&module_root, "cuenv", Some(options))
53 .map_err(|e| cuenv_core::Error::configuration(format!("CUE evaluation failed: {e}")))?;
54
55 let module = ModuleEvaluation::from_raw(
56 module_root.clone(),
57 raw_result.instances,
58 raw_result.projects,
59 );
60
61 let projects: Vec<DiscoveredCIProject> = module
63 .projects()
64 .filter_map(|instance| {
65 instance.deserialize().ok().map(|mut config: Project| {
66 config.expand_cross_project_references();
68
69 let env_cue_path = module_root.join(&instance.path).join("env.cue");
71
72 DiscoveredCIProject {
73 path: env_cue_path,
74 config,
75 }
76 })
77 })
78 .collect();
79
80 Ok(projects)
81}