cuenv_ci/executor/
config.rs

1//! CI Executor Configuration
2//!
3//! Configuration for the CI pipeline executor including cache settings,
4//! parallelism, and secret handling.
5
6use crate::ir::CachePolicy;
7use std::path::PathBuf;
8
9/// Default shell path for task execution
10pub const DEFAULT_SHELL: &str = "/bin/sh";
11
12/// Configuration for the CI executor
13#[derive(Debug, Clone)]
14pub struct CIExecutorConfig {
15    /// Project root directory
16    pub project_root: PathBuf,
17
18    /// Cache root directory (default: .cuenv/cache)
19    pub cache_root: Option<PathBuf>,
20
21    /// Maximum parallel tasks (0 = unlimited)
22    pub max_parallel: usize,
23
24    /// Capture stdout/stderr for reports
25    pub capture_output: bool,
26
27    /// Dry run mode (don't execute, just report what would run)
28    pub dry_run: bool,
29
30    /// Global cache policy override (for fork PRs -> Readonly)
31    pub cache_policy_override: Option<CachePolicy>,
32
33    /// Salt for secret fingerprinting (`CUENV_SECRET_SALT`)
34    pub secret_salt: Option<String>,
35
36    /// Previous salt for secret fingerprinting during rotation (`CUENV_SECRET_SALT_PREV`)
37    pub secret_salt_prev: Option<String>,
38
39    /// Shell path for shell-mode task execution (default: /bin/sh)
40    pub shell_path: String,
41}
42
43impl Default for CIExecutorConfig {
44    fn default() -> Self {
45        Self {
46            project_root: PathBuf::from("."),
47            cache_root: None,
48            max_parallel: 4,
49            capture_output: true,
50            dry_run: false,
51            cache_policy_override: None,
52            secret_salt: None,
53            secret_salt_prev: None,
54            shell_path: DEFAULT_SHELL.to_string(),
55        }
56    }
57}
58
59impl CIExecutorConfig {
60    /// Create a new config with the given project root
61    #[must_use]
62    pub fn new(project_root: PathBuf) -> Self {
63        Self {
64            project_root,
65            ..Default::default()
66        }
67    }
68
69    /// Set the cache root directory
70    #[must_use]
71    pub fn with_cache_root(mut self, cache_root: PathBuf) -> Self {
72        self.cache_root = Some(cache_root);
73        self
74    }
75
76    /// Set the maximum parallel tasks
77    #[must_use]
78    pub fn with_max_parallel(mut self, max_parallel: usize) -> Self {
79        self.max_parallel = max_parallel;
80        self
81    }
82
83    /// Enable or disable output capture
84    #[must_use]
85    pub fn with_capture_output(mut self, capture: bool) -> Self {
86        self.capture_output = capture;
87        self
88    }
89
90    /// Enable or disable dry run mode
91    #[must_use]
92    pub fn with_dry_run(mut self, dry_run: bool) -> Self {
93        self.dry_run = dry_run;
94        self
95    }
96
97    /// Set a global cache policy override
98    #[must_use]
99    pub fn with_cache_policy_override(mut self, policy: CachePolicy) -> Self {
100        self.cache_policy_override = Some(policy);
101        self
102    }
103
104    /// Set the secret salt for fingerprinting
105    #[must_use]
106    pub fn with_secret_salt(mut self, salt: String) -> Self {
107        self.secret_salt = Some(salt);
108        self
109    }
110
111    /// Set the previous secret salt for rotation
112    #[must_use]
113    pub fn with_secret_salt_prev(mut self, salt: String) -> Self {
114        self.secret_salt_prev = Some(salt);
115        self
116    }
117
118    /// Set the shell path for shell-mode execution
119    #[must_use]
120    pub fn with_shell_path(mut self, shell_path: impl Into<String>) -> Self {
121        self.shell_path = shell_path.into();
122        self
123    }
124
125    /// Get the effective cache root path
126    #[must_use]
127    pub fn effective_cache_root(&self) -> PathBuf {
128        self.cache_root
129            .clone()
130            .unwrap_or_else(|| self.project_root.join(".cuenv/cache"))
131    }
132}
133
134#[cfg(test)]
135mod tests {
136    use super::*;
137
138    #[test]
139    fn test_default_config() {
140        let config = CIExecutorConfig::default();
141        assert_eq!(config.max_parallel, 4);
142        assert!(config.capture_output);
143        assert!(!config.dry_run);
144        assert!(config.cache_policy_override.is_none());
145    }
146
147    #[test]
148    fn test_builder_pattern() {
149        let config = CIExecutorConfig::new(PathBuf::from("/project"))
150            .with_max_parallel(8)
151            .with_dry_run(true)
152            .with_cache_policy_override(CachePolicy::Readonly);
153
154        assert_eq!(config.max_parallel, 8);
155        assert!(config.dry_run);
156        assert_eq!(config.cache_policy_override, Some(CachePolicy::Readonly));
157    }
158
159    #[test]
160    fn test_effective_cache_root() {
161        let config = CIExecutorConfig::new(PathBuf::from("/project"));
162        assert_eq!(
163            config.effective_cache_root(),
164            PathBuf::from("/project/.cuenv/cache")
165        );
166
167        let config_with_override = CIExecutorConfig::new(PathBuf::from("/project"))
168            .with_cache_root(PathBuf::from("/tmp/cache"));
169        assert_eq!(
170            config_with_override.effective_cache_root(),
171            PathBuf::from("/tmp/cache")
172        );
173    }
174
175    #[test]
176    fn test_shell_path_default() {
177        let config = CIExecutorConfig::default();
178        assert_eq!(config.shell_path, "/bin/sh");
179    }
180
181    #[test]
182    fn test_shell_path_custom() {
183        let config = CIExecutorConfig::new(PathBuf::from("/project")).with_shell_path("/bin/bash");
184        assert_eq!(config.shell_path, "/bin/bash");
185    }
186}