cuenv_ci/
discovery.rs

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
13/// Find the CUE module root by walking up from `start` looking for `cue.mod/` directory.
14fn 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
26/// Discover all projects in the current repository
27///
28/// # Errors
29/// Returns an error if glob pattern matching fails or if not inside a CUE module
30///
31/// # Panics
32/// Panics if the regex pattern is invalid (should not happen as it is hardcoded)
33pub fn discover_projects() -> Result<Vec<DiscoveredCIProject>> {
34    // Check if we're inside a CUE module first
35    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    // Use module-wide evaluation instead of per-project evaluation
48    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    // Iterate through all Project instances (schema-verified)
62    let projects: Vec<DiscoveredCIProject> = module
63        .projects()
64        .filter_map(|instance| {
65            instance.deserialize().ok().map(|mut config: Project| {
66                // Expand cross-project references and implicit dependencies
67                config.expand_cross_project_references();
68
69                // Build the path to the env.cue file
70                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}