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	env.entry("NODE_OPTIONS".to_string())
96		.or_insert("--max-old-space-size=16384".to_string());
97
98	// Default run configuration
99	env.entry("HOT_RELOAD".to_string()).or_insert("true".to_string());
100	env.entry("WATCH".to_string()).or_insert("true".to_string());
101	env.entry("LIVE_RELOAD_PORT".to_string())
102		.or_insert(DefaultLiveReloadPort.to_string());
103
104	// Default logging
105	env.entry("Level".to_string()).or_insert("silent".to_string());
106	env.entry("RUST_LOG".to_string()).or_insert("info".to_string());
107}
108
109/// Merges shell environment variables into the environment.
110///
111/// Only merges build system-related environment variables.
112///
113/// # Arguments
114///
115/// * `env` - The environment HashMap to merge into
116fn MergeShellEnv(env:&mut HashMap<String, String>) {
117	let RelevantVars = [
118		"Browser",
119		"Bundle",
120		"Clean",
121		"Compile",
122		"Debug",
123		"Dependency",
124		"Mountain",
125		"Wind",
126		"Electron",
127		"BrowserProxy",
128		"NODE_ENV",
129		"NODE_VERSION",
130		"NODE_OPTIONS",
131		"RUST_LOG",
132		"AIR_LOG_JSON",
133		"AIR_LOG_FILE",
134		"Level",
135		"HOT_RELOAD",
136		"WATCH",
137		"LIVE_RELOAD_PORT",
138	];
139
140	for Var in RelevantVars {
141		if let Ok(Value) = std::env::var(Var) {
142			env.insert(Var.to_string(), Value);
143		}
144	}
145}
146
147/// Validates environment variables for a run.
148///
149/// # Arguments
150///
151/// * `env` - The environment variables to validate
152///
153/// # Returns
154///
155/// A list of validation errors (empty if valid)
156pub fn Validate(Env:&HashMap<String, String>) -> Vec<String> {
157	let mut Errors = Vec::new();
158
159	// Check NODE_VERSION is set
160	if !Env.contains_key("NODE_VERSION") {
161		Errors.push("NODE_VERSION environment variable is required".to_string());
162	}
163
164	// Check NODE_ENV is set
165	if !Env.contains_key("NODE_ENV") {
166		Errors.push("NODE_ENV environment variable is required".to_string());
167	}
168
169	// Check at least one workbench is enabled
170	let Workbenches = ["Browser", "Wind", "Mountain", "Electron"];
171	let HasWorkbench = Workbenches.iter().any(|W| Env.get(*W).map(|V| V == "true").unwrap_or(false));
172
173	if !HasWorkbench {
174		Errors.push("At least one workbench must be enabled (Browser, Wind, Mountain, or Electron)".to_string());
175	}
176
177	// Check LIVE_RELOAD_PORT is valid
178	if let Some(PortStr) = Env.get("LIVE_RELOAD_PORT") {
179		if let Ok(Port) = PortStr.parse::<u16>() {
180			if Port == 0 {
181				Errors.push("LIVE_RELOAD_PORT cannot be 0".to_string());
182			}
183		} else {
184			Errors.push("LIVE_RELOAD_PORT must be a valid port number".to_string());
185		}
186	}
187
188	Errors
189}
190
191/// Gets the workbench type from environment variables.
192///
193/// # Arguments
194///
195/// * `env` - The environment variables
196///
197/// # Returns
198///
199/// The name of the enabled workbench, or None if none enabled
200pub fn get_workbench(env:&HashMap<String, String>) -> Option<String> {
201	let workbenches = [
202		("Browser", "Browser"),
203		("Wind", "Wind"),
204		("Mountain", "Mountain"),
205		("Electron", "Electron"),
206	];
207
208	for (var, name) in workbenches {
209		if env.get(var).map(|v| v == "true").unwrap_or(false) {
210			return Some(name.to_string());
211		}
212	}
213
214	None
215}
216
217/// Checks if debug mode is enabled.
218///
219/// # Arguments
220///
221/// * `env` - The environment variables
222///
223/// # Returns
224///
225/// True if debug mode is enabled
226pub fn is_debug(env:&HashMap<String, String>) -> bool { env.get(DebugEnv).map(|v| v == "true").unwrap_or(false) }
227
228/// Checks if hot-reload is enabled.
229///
230/// # Arguments
231///
232/// * `env` - The environment variables
233///
234/// # Returns
235///
236/// True if hot-reload is enabled
237pub fn is_hot_reload_enabled(env:&HashMap<String, String>) -> bool {
238	env.get(HotReloadEnv).map(|v| v == "true").unwrap_or(true)
239}
240
241/// Checks if watch mode is enabled.
242///
243/// # Arguments
244///
245/// * `env` - The environment variables
246///
247/// # Returns
248///
249/// True if watch mode is enabled
250pub fn is_watch_enabled(env:&HashMap<String, String>) -> bool { env.get(WatchEnv).map(|v| v == "true").unwrap_or(true) }
251
252/// Formats environment variables for display.
253///
254/// # Arguments
255///
256/// * `env` - The environment variables to format
257///
258/// # Returns
259///
260/// A formatted string representation
261pub fn format_for_display(env:&HashMap<String, String>) -> String {
262	let mut lines:Vec<String> = env
263		.iter()
264		.map(|(k, v)| {
265			let display_value = if v.is_empty() { "(empty)".to_string() } else { v.clone() };
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_",
286		"HOT_",
287		"WATCH",
288		"LIVE_",
289		"AIR_",
290		"RUST_",
291		"Browser",
292		"Bundle",
293		"Clean",
294		"Compile",
295		"Debug",
296		"Dependency",
297		"Mountain",
298		"Wind",
299		"Electron",
300		"Level",
301	];
302
303	env.iter()
304		.filter(|(k, _)| run_prefixes.iter().any(|prefix| k.starts_with(prefix)))
305		.map(|(k, v)| (k.clone(), v.clone()))
306		.collect()
307}