envvault/cli/commands/
env_list.rs1use std::fs;
4
5use comfy_table::{ContentArrangement, Table};
6use console::style;
7
8use crate::cli::output;
9use crate::cli::Cli;
10use crate::errors::Result;
11
12pub fn execute(cli: &Cli) -> Result<()> {
14 let cwd = std::env::current_dir()?;
15 let vault_dir = cwd.join(&cli.vault_dir);
16
17 if !vault_dir.exists() {
18 output::info("No vault directory found.");
19 output::tip("Run `envvault init` to create a vault.");
20 return Ok(());
21 }
22
23 let mut envs = list_environments(&vault_dir)?;
24 envs.sort_by(|a, b| a.name.cmp(&b.name));
25
26 if envs.is_empty() {
27 output::info("No environments found.");
28 output::tip("Run `envvault init` to create your first vault.");
29 return Ok(());
30 }
31
32 let mut table = Table::new();
33 table.set_content_arrangement(ContentArrangement::Dynamic);
34 table.set_header(vec!["Environment", "Size", "Active"]);
35
36 for env in &envs {
37 let active = if env.name == cli.env {
38 style("*").green().bold().to_string()
39 } else {
40 String::new()
41 };
42
43 table.add_row(vec![env.name.clone(), format_size(env.size), active]);
44 }
45
46 output::info(&format!("{} environment(s) found:", envs.len()));
47 println!("{table}");
48
49 Ok(())
50}
51
52pub struct EnvInfo {
54 pub name: String,
55 pub size: u64,
56}
57
58pub fn list_environments(vault_dir: &std::path::Path) -> Result<Vec<EnvInfo>> {
60 let mut envs = Vec::new();
61
62 let entries = fs::read_dir(vault_dir)?;
63 for entry in entries {
64 let entry = entry?;
65 let path = entry.path();
66
67 if let Some(ext) = path.extension() {
68 if ext == "vault" {
69 if let Some(stem) = path.file_stem() {
70 let name = stem.to_string_lossy().to_string();
71 let size = entry.metadata().map(|m| m.len()).unwrap_or(0);
72 envs.push(EnvInfo { name, size });
73 }
74 }
75 }
76 }
77
78 Ok(envs)
79}
80
81#[allow(clippy::cast_precision_loss)] fn format_size(bytes: u64) -> String {
84 if bytes < 1024 {
85 format!("{bytes} B")
86 } else if bytes < 1024 * 1024 {
87 format!("{:.1} KB", bytes as f64 / 1024.0)
88 } else {
89 format!("{:.1} MB", bytes as f64 / (1024.0 * 1024.0))
90 }
91}
92
93#[cfg(test)]
94mod tests {
95 use super::*;
96
97 #[test]
98 fn format_size_bytes() {
99 assert_eq!(format_size(512), "512 B");
100 }
101
102 #[test]
103 fn format_size_kilobytes() {
104 assert_eq!(format_size(2048), "2.0 KB");
105 }
106
107 #[test]
108 fn format_size_megabytes() {
109 assert_eq!(format_size(2 * 1024 * 1024), "2.0 MB");
110 }
111
112 #[test]
113 fn list_environments_from_dir() {
114 let dir = tempfile::TempDir::new().unwrap();
115 std::fs::write(dir.path().join("dev.vault"), b"test").unwrap();
117 std::fs::write(dir.path().join("staging.vault"), b"test data").unwrap();
118 std::fs::write(dir.path().join("not-a-vault.txt"), b"nope").unwrap();
119
120 let envs = list_environments(dir.path()).unwrap();
121 assert_eq!(envs.len(), 2);
122
123 let names: Vec<&str> = envs.iter().map(|e| e.name.as_str()).collect();
124 assert!(names.contains(&"dev"));
125 assert!(names.contains(&"staging"));
126 }
127}