Maintain/Run/
Environment.rs

1//=============================================================================//
2// File Path: Element/Maintain/Source/Run/Environment.rs
3//=============================================================================//
4// Module: Environment
5//
6// Brief Description: Environment variable management for run operations.
7//
8// RESPONSIBILITIES:
9// ================
10//
11// Primary:
12// - Resolve environment variables from profiles
13// - Merge environment variables from multiple sources
14// - Validate environment configuration
15//
16// Secondary:
17// - Provide environment variable templates
18// - Support environment inheritance
19//
20// ARCHITECTURAL ROLE:
21// ===================
22//
23// Position:
24// - Infrastructure/Environment management layer
25// - Environment resolution
26//
27// Dependencies (What this module requires):
28// - External crates: std
29// - Internal modules: Constant, Definition, Error
30// - Traits implemented: None
31//
32// Dependents (What depends on this module):
33// - Run CLI module
34// - Run Process module
35//
36//=============================================================================//
37// IMPLEMENTATION
38//=============================================================================//
39
40use crate::Run::Constant::*;
41use crate::Run::Definition::Profile;
42use crate::Run::Error::Result;
43
44use std::collections::HashMap;
45
46/// Resolves environment variables for a run profile.
47///
48/// This function combines environment variables from multiple sources:
49/// 1. Template defaults
50/// 2. Shell environment (if enabled)
51/// 3. Profile-specific variables
52/// 4. CLI overrides
53///
54/// # Arguments
55///
56/// * `profile` - The profile to resolve environment for
57/// * `merge_shell` - Whether to merge with shell environment
58/// * `overrides` - CLI-provided environment overrides
59///
60/// # Returns
61///
62/// A HashMap of resolved environment variables
63pub fn resolve(
64    profile: &Profile,
65    merge_shell: bool,
66    overrides: &[(String, String)],
67) -> Result<HashMap<String, String>> {
68    let mut env = HashMap::new();
69
70    // Layer 1: Template defaults
71    apply_template_defaults(&mut env);
72
73    // Layer 2: Shell environment (if enabled)
74    if merge_shell {
75        merge_shell_env(&mut env);
76    }
77
78    // Layer 3: Profile environment
79    if let Some(profile_env) = &profile.env {
80        for (key, value) in profile_env {
81            env.insert(key.clone(), value.clone());
82        }
83    }
84
85    // Layer 4: CLI overrides (highest priority)
86    for (key, value) in overrides {
87        env.insert(key.clone(), value.clone());
88    }
89
90    Ok(env)
91}
92
93/// Applies template default environment variables.
94///
95/// # Arguments
96///
97/// * `env` - The environment HashMap to populate
98fn apply_template_defaults(env: &mut HashMap<String, String>) {
99    // Default Node.js configuration
100    env.entry("NODE_VERSION".to_string()).or_insert("22".to_string());
101    env.entry("NODE_OPTIONS".to_string())
102        .or_insert("--max-old-space-size=16384".to_string());
103
104    // Default run configuration
105    env.entry("HOT_RELOAD".to_string()).or_insert("true".to_string());
106    env.entry("WATCH".to_string()).or_insert("true".to_string());
107    env.entry("LIVE_RELOAD_PORT".to_string())
108        .or_insert(DefaultLiveReloadPort.to_string());
109
110    // Default logging
111    env.entry("Level".to_string()).or_insert("silent".to_string());
112    env.entry("RUST_LOG".to_string()).or_insert("info".to_string());
113}
114
115/// Merges shell environment variables into the environment.
116///
117/// Only merges build system-related environment variables.
118///
119/// # Arguments
120///
121/// * `env` - The environment HashMap to merge into
122fn merge_shell_env(env: &mut HashMap<String, String>) {
123    let relevant_vars = [
124        "Browser", "Bundle", "Clean", "Compile", "Debug", "Dependency",
125        "Mountain", "Wind", "Electron", "BrowserProxy",
126        "NODE_ENV", "NODE_VERSION", "NODE_OPTIONS",
127        "RUST_LOG", "AIR_LOG_JSON", "AIR_LOG_FILE", "Level",
128        "HOT_RELOAD", "WATCH", "LIVE_RELOAD_PORT",
129    ];
130
131    for var in relevant_vars {
132        if let Ok(value) = std::env::var(var) {
133            env.insert(var.to_string(), value);
134        }
135    }
136}
137
138/// Validates environment variables for a run.
139///
140/// # Arguments
141///
142/// * `env` - The environment variables to validate
143///
144/// # Returns
145///
146/// A list of validation errors (empty if valid)
147pub fn validate(env: &HashMap<String, String>) -> Vec<String> {
148    let mut errors = Vec::new();
149
150    // Check NODE_VERSION is set
151    if !env.contains_key("NODE_VERSION") {
152        errors.push("NODE_VERSION environment variable is required".to_string());
153    }
154
155    // Check NODE_ENV is set
156    if !env.contains_key("NODE_ENV") {
157        errors.push("NODE_ENV environment variable is required".to_string());
158    }
159
160    // Check at least one workbench is enabled
161    let workbenches = ["Browser", "Wind", "Mountain", "Electron"];
162    let has_workbench = workbenches.iter().any(|w| {
163        env.get(*w).map(|v| v == "true").unwrap_or(false)
164    });
165
166    if !has_workbench {
167        errors.push("At least one workbench must be enabled (Browser, Wind, Mountain, or Electron)".to_string());
168    }
169
170    // Check LIVE_RELOAD_PORT is valid
171    if let Some(port_str) = env.get("LIVE_RELOAD_PORT") {
172        if let Ok(port) = port_str.parse::<u16>() {
173            if port == 0 {
174                errors.push("LIVE_RELOAD_PORT cannot be 0".to_string());
175            }
176        } else {
177            errors.push("LIVE_RELOAD_PORT must be a valid port number".to_string());
178        }
179    }
180
181    errors
182}
183
184/// Gets the workbench type from environment variables.
185///
186/// # Arguments
187///
188/// * `env` - The environment variables
189///
190/// # Returns
191///
192/// The name of the enabled workbench, or None if none enabled
193pub fn get_workbench(env: &HashMap<String, String>) -> Option<String> {
194    let workbenches = [
195        ("Browser", "Browser"),
196        ("Wind", "Wind"),
197        ("Mountain", "Mountain"),
198        ("Electron", "Electron"),
199    ];
200
201    for (var, name) in workbenches {
202        if env.get(var).map(|v| v == "true").unwrap_or(false) {
203            return Some(name.to_string());
204        }
205    }
206
207    None
208}
209
210/// Checks if debug mode is enabled.
211///
212/// # Arguments
213///
214/// * `env` - The environment variables
215///
216/// # Returns
217///
218/// True if debug mode is enabled
219pub fn is_debug(env: &HashMap<String, String>) -> bool {
220    env.get(DebugEnv).map(|v| v == "true").unwrap_or(false)
221}
222
223/// Checks if hot-reload is enabled.
224///
225/// # Arguments
226///
227/// * `env` - The environment variables
228///
229/// # Returns
230///
231/// True if hot-reload is enabled
232pub fn is_hot_reload_enabled(env: &HashMap<String, String>) -> bool {
233    env.get(HotReloadEnv).map(|v| v == "true").unwrap_or(true)
234}
235
236/// Checks if watch mode is enabled.
237///
238/// # Arguments
239///
240/// * `env` - The environment variables
241///
242/// # Returns
243///
244/// True if watch mode is enabled
245pub fn is_watch_enabled(env: &HashMap<String, String>) -> bool {
246    env.get(WatchEnv).map(|v| v == "true").unwrap_or(true)
247}
248
249/// Formats environment variables for display.
250///
251/// # Arguments
252///
253/// * `env` - The environment variables to format
254///
255/// # Returns
256///
257/// A formatted string representation
258pub fn format_for_display(env: &HashMap<String, String>) -> String {
259    let mut lines: Vec<String> = env.iter()
260        .map(|(k, v)| {
261            let display_value = if v.is_empty() {
262                "(empty)".to_string()
263            } else {
264                v.clone()
265            };
266            format!("  {} = {}", k, display_value)
267        })
268        .collect();
269
270    lines.sort();
271    lines.join("\n")
272}
273
274/// Filters environment variables to only include run-related ones.
275///
276/// # Arguments
277///
278/// * `env` - The full environment
279///
280/// # Returns
281///
282/// A filtered HashMap with only run-related variables
283pub fn filter_run_vars(env: &HashMap<String, String>) -> HashMap<String, String> {
284    let run_prefixes = [
285        "NODE_", "HOT_", "WATCH", "LIVE_", "AIR_", "RUST_",
286        "Browser", "Bundle", "Clean", "Compile", "Debug",
287        "Dependency", "Mountain", "Wind", "Electron", "Level",
288    ];
289
290    env.iter()
291        .filter(|(k, _)| {
292            run_prefixes.iter().any(|prefix| k.starts_with(prefix))
293        })
294        .map(|(k, v)| (k.clone(), v.clone()))
295        .collect()
296}