# Environment detection and management helper functions # NUSHELL 0.109 COMPLIANT - Using do-complete (Rule 5), each (Rule 8) # Detect current environment from system context # Priority: PROVISIONING_ENV > CI/CD > git/dev markers > HOSTNAME > NODE_ENV > TERM > default export def detect-current-environment [] { # Check explicit environment variable if ($env.PROVISIONING_ENV? | is-not-empty) { return $env.PROVISIONING_ENV } # Check CI/CD environments if ($env.CI? | is-not-empty) { if ($env.GITHUB_ACTIONS? | is-not-empty) { return "ci" } if ($env.GITLAB_CI? | is-not-empty) { return "ci" } if ($env.JENKINS_URL? | is-not-empty) { return "ci" } return "test" } # Check for development indicators if (($env.PWD | path join ".git" | path exists) or ($env.PWD | path join "development" | path exists) or ($env.PWD | path join "dev" | path exists)) { return "dev" } # Check for production indicators if (($env.HOSTNAME? | default "" | str contains "prod") or ($env.NODE_ENV? | default "" | str downcase) == "production" or ($env.ENVIRONMENT? | default "" | str downcase) == "production") { return "prod" } # Check for test indicators if (($env.NODE_ENV? | default "" | str downcase) == "test" or ($env.ENVIRONMENT? | default "" | str downcase) == "test") { return "test" } # Default to development for interactive usage if ($env.TERM? | is-not-empty) { return "dev" } # Fallback "dev" } # Get available environments from configuration export def get-available-environments [config: record] { let env_section_result = (do { $config | get "environments" } | complete) let environments_section = if $env_section_result.exit_code == 0 { $env_section_result.stdout } else { {} } $environments_section | columns } # Validate environment name export def validate-environment [environment: string, config: record] { let valid_environments = ["dev" "test" "prod" "ci" "staging" "local"] let configured_environments = (get-available-environments $config) let all_valid = ($valid_environments | append $configured_environments | uniq) if ($environment in $all_valid) { { valid: true, message: "" } } else { { valid: false, message: $"Invalid environment '($environment)'. Valid options: ($all_valid | str join ', ')" } } } # Set a configuration value using dot notation path (e.g., "debug.log_level") def set-config-value [config: record, path: string, value: any] { let path_parts = ($path | split row ".") match ($path_parts | length) { 1 => { $config | upsert ($path_parts | first) $value } 2 => { let section = ($path_parts | first) let key = ($path_parts | last) let section_result = (do { $config | get $section } | complete) let section_data = if $section_result.exit_code == 0 { $section_result.stdout } else { {} } $config | upsert $section ($section_data | upsert $key $value) } 3 => { let section = ($path_parts | first) let subsection = ($path_parts | get 1) let key = ($path_parts | last) let section_result = (do { $config | get $section } | complete) let section_data = if $section_result.exit_code == 0 { $section_result.stdout } else { {} } let subsection_result = (do { $section_data | get $subsection } | complete) let subsection_data = if $subsection_result.exit_code == 0 { $subsection_result.stdout } else { {} } $config | upsert $section ($section_data | upsert $subsection ($subsection_data | upsert $key $value)) } _ => { # For deeper nesting, use recursive approach set-config-value-recursive $config $path_parts $value } } } # Recursive helper for deep config value setting def set-config-value-recursive [config: record, path_parts: list, value: any] { if ($path_parts | length) == 1 { $config | upsert ($path_parts | first) $value } else { let current_key = ($path_parts | first) let remaining_parts = ($path_parts | skip 1) let current_result = (do { $config | get $current_key } | complete) let current_section = if $current_result.exit_code == 0 { $current_result.stdout } else { {} } $config | upsert $current_key (set-config-value-recursive $current_section $remaining_parts $value) } } # Apply environment variable overrides to configuration export def apply-environment-variable-overrides [config: record, debug = false] { # Map of environment variables to config paths with type conversion let env_mappings = { "PROVISIONING_DEBUG": { path: "debug.enabled", type: "bool" }, "PROVISIONING_LOG_LEVEL": { path: "debug.log_level", type: "string" }, "PROVISIONING_NO_TERMINAL": { path: "debug.no_terminal", type: "bool" }, "PROVISIONING_CHECK": { path: "debug.check", type: "bool" }, "PROVISIONING_METADATA": { path: "debug.metadata", type: "bool" }, "PROVISIONING_OUTPUT_FORMAT": { path: "output.format", type: "string" }, "PROVISIONING_FILE_VIEWER": { path: "output.file_viewer", type: "string" }, "PROVISIONING_USE_SOPS": { path: "sops.use_sops", type: "bool" }, "PROVISIONING_PROVIDER": { path: "providers.default", type: "string" }, "PROVISIONING_WORKSPACE_PATH": { path: "paths.workspace", type: "string" }, "PROVISIONING_INFRA_PATH": { path: "paths.infra", type: "string" }, "PROVISIONING_SOPS": { path: "sops.config_path", type: "string" }, "PROVISIONING_KAGE": { path: "sops.age_key_file", type: "string" } } # Use reduce --fold to process all env mappings (Rule 3: no mutable variables) $env_mappings | columns | reduce --fold $config {|env_var, result| let env_result = (do { $env | get $env_var } | complete) let env_value = if $env_result.exit_code == 0 { $env_result.stdout } else { null } if ($env_value | is-not-empty) { let mapping = ($env_mappings | get $env_var) let config_path = $mapping.path let config_type = $mapping.type # Convert value to appropriate type let converted_value = match $config_type { "bool" => { if ($env_value | describe) == "string" { match ($env_value | str downcase) { "true" | "1" | "yes" | "on" => true "false" | "0" | "no" | "off" => false _ => false } } else { $env_value | into bool } } "string" => $env_value _ => $env_value } if $debug { # log debug $"Applying env override: ($env_var) -> ($config_path) = ($converted_value)" } (set-config-value $result $config_path $converted_value) } else { $result } } }