envx_secure/commands/
diff.rs1use crate::parser;
8use anyhow::Result;
9use owo_colors::OwoColorize;
10use std::path::Path;
11
12const SENSITIVE_PATTERNS: &[&str] = &["SECRET", "KEY", "TOKEN", "PASSWORD", "PASS", "PWD"];
14
15fn is_sensitive(key: &str) -> bool {
16 let upper = key.to_uppercase();
17 SENSITIVE_PATTERNS.iter().any(|p| upper.contains(p))
18}
19
20fn redact(value: &str) -> String {
21 "•".repeat(value.len().max(7))
22}
23
24pub fn run(file_a: &Path, file_b: &Path) -> Result<()> {
39 let map_a = parser::parse(file_a)?;
40 let map_b = parser::parse(file_b)?;
41
42 println!("{}", format!("--- {}", file_a.display()).bold());
43 println!("{}", format!("+++ {}", file_b.display()).bold());
44
45 let mut differences = false;
46
47 let mut all_keys: Vec<&str> = map_a.keys().map(String::as_str).collect();
49 for k in map_b.keys() {
50 if !map_a.contains_key(k.as_str()) {
51 all_keys.push(k.as_str());
52 }
53 }
54
55 for key in all_keys {
56 match (map_a.get(key), map_b.get(key)) {
57 (Some(va), Some(vb)) if va == vb => {
58 println!(" {}", format!("{key}={va}").dimmed());
59 }
60 (Some(va), Some(vb)) => {
61 differences = true;
62 if is_sensitive(key) {
63 println!(
64 "{}",
65 format!("~ {key}={} → {}", redact(va), redact(vb)).yellow()
66 );
67 } else {
68 println!("{}", format!("- {key}={va}").red());
69 println!("{}", format!("+ {key}={vb}").green());
70 }
71 }
72 (Some(va), None) => {
73 differences = true;
74 println!("{}", format!("- {key}={va}").red());
75 }
76 (None, Some(vb)) => {
77 differences = true;
78 println!("{}", format!("+ {key}={vb}").green());
79 }
80 (None, None) => unreachable!(),
81 }
82 }
83
84 if differences {
85 std::process::exit(1);
86 }
87
88 Ok(())
89}