use crate::commands::{CommandExecutor, relative_path_from_root};
use cuenv_core::Result;
use cuenv_core::manifest::Base;
use std::path::Path;
use tracing::instrument;
fn load_base_config(path: &str, executor: &CommandExecutor) -> Result<Base> {
let target_path = Path::new(path)
.canonicalize()
.map_err(|e| cuenv_core::Error::Io {
source: e,
path: Some(Path::new(path).to_path_buf().into_boxed_path()),
operation: "canonicalize path".to_string(),
})?;
tracing::debug!("Using cached module evaluation from executor");
let module = executor.get_module(&target_path)?;
let relative_path = relative_path_from_root(&module.root, &target_path);
let instance = module.get(&relative_path).ok_or_else(|| {
cuenv_core::Error::configuration(format!(
"No CUE instance found at path: {} (relative: {})",
target_path.display(),
relative_path.display()
))
})?;
instance.deserialize()
}
#[instrument(name = "env_list", skip(executor))]
pub async fn execute_env_list(
path: &str,
format: &str,
executor: &CommandExecutor,
) -> Result<String> {
tracing::info!("Starting env list command");
tracing::debug!("Loading CUE config at path '{}'", path);
let manifest: Base = load_base_config(path, executor)?;
let environments: Vec<String> = manifest
.env
.and_then(|env| env.environment)
.map(|envs| {
let mut keys: Vec<String> = envs.keys().cloned().collect();
keys.sort();
keys
})
.unwrap_or_default();
let output = match format {
"json" => serde_json::to_string_pretty(&environments)
.map_err(|e| cuenv_core::Error::configuration(format!("Failed to format JSON: {e}")))?,
"simple" => environments.join("\n"),
other => {
return Err(cuenv_core::Error::configuration(format!(
"Unsupported format: '{other}'. Supported formats are 'json' and 'simple'."
)));
}
};
tracing::info!("Env list command completed successfully");
Ok(output)
}
#[instrument(name = "env_print", skip(executor))]
pub async fn execute_env_print(
path: &str,
format: &str,
environment: Option<&str>,
executor: &CommandExecutor,
) -> Result<String> {
tracing::info!("Starting env print command");
tracing::debug!("Loading CUE config at path '{}'", path);
let manifest: Base = load_base_config(path, executor)?;
let env = manifest.env.ok_or_else(|| {
cuenv_core::Error::configuration("No 'env' field found in CUE package".to_string())
})?;
let env_vars = if let Some(env_name) = environment {
tracing::debug!("Applying environment-specific overrides for '{}'", env_name);
env.for_environment(env_name)
} else {
env.base.clone()
};
let (resolved_vars, secrets) =
cuenv_core::environment::Environment::resolve_all_with_secrets(&env_vars).await?;
cuenv_events::register_secrets(secrets.into_iter());
let output = match format {
"json" => serde_json::to_string_pretty(&resolved_vars)
.map_err(|e| cuenv_core::Error::configuration(format!("Failed to format JSON: {e}")))?,
"env" | "simple" => format_as_env_vars(&resolved_vars),
other => {
return Err(cuenv_core::Error::configuration(format!(
"Unsupported format: '{other}'. Supported formats are 'json', 'env', and 'simple'."
)));
}
};
tracing::info!("Env print command completed successfully");
Ok(output)
}
#[must_use]
fn format_as_env_vars(env_map: &std::collections::HashMap<String, String>) -> String {
let mut lines = Vec::new();
let mut keys: Vec<&String> = env_map.keys().collect();
keys.sort();
for key in keys {
let value = &env_map[key];
lines.push(format!("{key}={value}"));
}
lines.join("\n")
}
#[cfg(test)]
mod tests {
use super::*;
use std::collections::HashMap;
#[test]
fn test_format_as_env_vars_basic() {
let mut env_map = HashMap::new();
env_map.insert(
"DATABASE_URL".to_string(),
"postgres://localhost/mydb".to_string(),
);
env_map.insert("DEBUG".to_string(), "true".to_string());
env_map.insert("PORT".to_string(), "3000".to_string());
let result = format_as_env_vars(&env_map);
let lines: Vec<&str> = result.split('\n').collect();
assert_eq!(lines.len(), 3);
assert!(lines.contains(&"DATABASE_URL=postgres://localhost/mydb"));
assert!(lines.contains(&"DEBUG=true"));
assert!(lines.contains(&"PORT=3000"));
}
#[test]
fn test_format_as_env_vars_empty() {
let env_map: HashMap<String, String> = HashMap::new();
let result = format_as_env_vars(&env_map);
assert_eq!(result, "");
}
}