Skip to main content

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 std::collections::HashMap;
41
42use crate::Run::{Constant::*, Definition::Profile, Error::Result};
43
44/// Resolves environment variables for a run profile.
45///
46/// This function combines environment variables from multiple sources:
47/// 1. Template defaults
48/// 2. Shell environment (if enabled)
49/// 3. Profile-specific variables
50/// 4. CLI overrides
51///
52/// # Arguments
53///
54/// * `profile` - The profile to resolve environment for
55/// * `merge_shell` - Whether to merge with shell environment
56/// * `overrides` - CLI-provided environment overrides
57///
58/// # Returns
59///
60/// A HashMap of resolved environment variables
61pub fn Resolve(Profile:&Profile, MergeShell:bool, Overrides:&[(String, String)]) -> Result<HashMap<String, String>> {
62	let mut env = HashMap::new();
63
64	// Layer 1: Template defaults
65	ApplyTemplateDefaults(&mut env);
66
67	// Layer 2: Shell environment (if enabled)
68	if MergeShell {
69		MergeShellEnv(&mut env);
70	}
71
72	// Layer 3: Profile environment
73	if let Some(ProfileEnvironment) = &Profile.env {
74		for (Key, Value) in ProfileEnvironment {
75			env.insert(Key.clone(), Value.clone());
76		}
77	}
78
79	// Layer 4: CLI overrides (highest priority)
80	for (Key, Value) in Overrides {
81		env.insert(Key.clone(), Value.clone());
82	}
83
84	Ok(env)
85}
86
87/// Applies template default environment variables.
88///
89/// # Arguments
90///
91/// * `env` - The environment HashMap to populate
92fn ApplyTemplateDefaults(env:&mut HashMap<String, String>) {
93	// Default Node.js configuration
94	env.entry("NODE_VERSION".to_string()).or_insert("22".to_string());
95
96	env.entry("NODE_OPTIONS".to_string())
97		.or_insert("--max-old-space-size=16384".to_string());
98
99	// Default run configuration
100	env.entry("HOT_RELOAD".to_string()).or_insert("true".to_string());
101
102	env.entry("WATCH".to_string()).or_insert("true".to_string());
103
104	env.entry("LIVE_RELOAD_PORT".to_string())
105		.or_insert(DefaultLiveReloadPort.to_string());
106
107	// Default logging
108	env.entry("Level".to_string()).or_insert("silent".to_string());
109
110	env.entry("RUST_LOG".to_string()).or_insert("info".to_string());
111}
112
113/// Merges shell environment variables into the environment.
114///
115/// Only merges build system-related environment variables.
116///
117/// # Arguments
118///
119/// * `env` - The environment HashMap to merge into
120fn MergeShellEnv(env:&mut HashMap<String, String>) {
121	let RelevantVars = [
122		"Browser",
123		"Bundle",
124		"Clean",
125		"Compile",
126		"Debug",
127		"Dependency",
128		"Mountain",
129		"Wind",
130		"Electron",
131		"BrowserProxy",
132		"NODE_ENV",
133		"NODE_VERSION",
134		"NODE_OPTIONS",
135		"RUST_LOG",
136		"AIR_LOG_JSON",
137		"AIR_LOG_FILE",
138		"Level",
139		"HOT_RELOAD",
140		"WATCH",
141		"LIVE_RELOAD_PORT",
142	];
143
144	for Var in RelevantVars {
145		if let Ok(Value) = std::env::var(Var) {
146			env.insert(Var.to_string(), Value);
147		}
148	}
149}
150
151/// Validates environment variables for a run.
152///
153/// # Arguments
154///
155/// * `env` - The environment variables to validate
156///
157/// # Returns
158///
159/// A list of validation errors (empty if valid)
160pub fn Validate(Env:&HashMap<String, String>) -> Vec<String> {
161	let mut Errors = Vec::new();
162
163	// Check NODE_VERSION is set
164	if !Env.contains_key("NODE_VERSION") {
165		Errors.push("NODE_VERSION environment variable is required".to_string());
166	}
167
168	// Check NODE_ENV is set
169	if !Env.contains_key("NODE_ENV") {
170		Errors.push("NODE_ENV environment variable is required".to_string());
171	}
172
173	// Check at least one workbench is enabled
174	let Workbenches = ["Browser", "Wind", "Mountain", "Electron"];
175
176	let HasWorkbench = Workbenches.iter().any(|W| Env.get(*W).map(|V| V == "true").unwrap_or(false));
177
178	if !HasWorkbench {
179		Errors.push("At least one workbench must be enabled (Browser, Wind, Mountain, or Electron)".to_string());
180	}
181
182	// Check LIVE_RELOAD_PORT is valid
183	if let Some(PortStr) = Env.get("LIVE_RELOAD_PORT") {
184		if let Ok(Port) = PortStr.parse::<u16>() {
185			if Port == 0 {
186				Errors.push("LIVE_RELOAD_PORT cannot be 0".to_string());
187			}
188		} else {
189			Errors.push("LIVE_RELOAD_PORT must be a valid port number".to_string());
190		}
191	}
192
193	Errors
194}
195
196/// Gets the workbench type from environment variables.
197///
198/// # Arguments
199///
200/// * `env` - The environment variables
201///
202/// # Returns
203///
204/// The name of the enabled workbench, or None if none enabled
205pub fn get_workbench(env:&HashMap<String, String>) -> Option<String> {
206	let workbenches = [
207		("Browser", "Browser"),
208		("Wind", "Wind"),
209		("Mountain", "Mountain"),
210		("Electron", "Electron"),
211	];
212
213	for (var, name) in workbenches {
214		if env.get(var).map(|v| v == "true").unwrap_or(false) {
215			return Some(name.to_string());
216		}
217	}
218
219	None
220}
221
222/// Checks if debug mode is enabled.
223///
224/// # Arguments
225///
226/// * `env` - The environment variables
227///
228/// # Returns
229///
230/// True if debug mode is enabled
231pub fn is_debug(env:&HashMap<String, String>) -> bool { env.get(DebugEnv).map(|v| v == "true").unwrap_or(false) }
232
233/// Checks if hot-reload is enabled.
234///
235/// # Arguments
236///
237/// * `env` - The environment variables
238///
239/// # Returns
240///
241/// True if hot-reload is enabled
242pub fn is_hot_reload_enabled(env:&HashMap<String, String>) -> bool {
243	env.get(HotReloadEnv).map(|v| v == "true").unwrap_or(true)
244}
245
246/// Checks if watch mode is enabled.
247///
248/// # Arguments
249///
250/// * `env` - The environment variables
251///
252/// # Returns
253///
254/// True if watch mode is enabled
255pub fn is_watch_enabled(env:&HashMap<String, String>) -> bool { env.get(WatchEnv).map(|v| v == "true").unwrap_or(true) }
256
257/// Formats environment variables for display.
258///
259/// # Arguments
260///
261/// * `env` - The environment variables to format
262///
263/// # Returns
264///
265/// A formatted string representation
266pub fn format_for_display(env:&HashMap<String, String>) -> String {
267	let mut lines:Vec<String> = env
268		.iter()
269		.map(|(k, v)| {
270			let display_value = if v.is_empty() { "(empty)".to_string() } else { v.clone() };
271			format!("  {} = {}", k, display_value)
272		})
273		.collect();
274
275	lines.sort();
276
277	lines.join("\n")
278}
279
280/// Filters environment variables to only include run-related ones.
281///
282/// # Arguments
283///
284/// * `env` - The full environment
285///
286/// # Returns
287///
288/// A filtered HashMap with only run-related variables
289pub fn filter_run_vars(env:&HashMap<String, String>) -> HashMap<String, String> {
290	let run_prefixes = [
291		"NODE_",
292		"HOT_",
293		"WATCH",
294		"LIVE_",
295		"AIR_",
296		"RUST_",
297		"Browser",
298		"Bundle",
299		"Clean",
300		"Compile",
301		"Debug",
302		"Dependency",
303		"Mountain",
304		"Wind",
305		"Electron",
306		"Level",
307	];
308
309	env.iter()
310		.filter(|(k, _)| run_prefixes.iter().any(|prefix| k.starts_with(prefix)))
311		.map(|(k, v)| (k.clone(), v.clone()))
312		.collect()
313}