vika_cli/
formatter.rs

1use crate::error::Result;
2use std::path::Path;
3use std::process::Command;
4
5pub enum Formatter {
6    Prettier,
7    Biome,
8}
9
10pub struct FormatterManager;
11
12impl FormatterManager {
13    pub fn detect_formatter() -> Option<Formatter> {
14        Self::detect_formatter_from_dir(Path::new("."))
15    }
16
17    pub fn detect_formatter_from_dir(dir: &Path) -> Option<Formatter> {
18        // Check for prettier
19        if Self::has_prettier_in_dir(dir) {
20            return Some(Formatter::Prettier);
21        }
22
23        // Check for biome
24        if Self::has_biome_in_dir(dir) {
25            return Some(Formatter::Biome);
26        }
27
28        None
29    }
30
31    fn has_prettier_in_dir(dir: &Path) -> bool {
32        // Check for package.json with prettier
33        let package_json = dir.join("package.json");
34        if package_json.exists() {
35            if let Ok(content) = std::fs::read_to_string(&package_json) {
36                if content.contains("prettier") {
37                    return true;
38                }
39            }
40        }
41
42        // Check for prettier config files
43        dir.join(".prettierrc").exists()
44            || dir.join(".prettierrc.json").exists()
45            || dir.join(".prettierrc.js").exists()
46            || dir.join("prettier.config.js").exists()
47    }
48
49    fn has_biome_in_dir(dir: &Path) -> bool {
50        // Check for biome.json
51        dir.join("biome.json").exists() || dir.join("biome.jsonc").exists()
52    }
53
54    pub fn format_files(files: &[std::path::PathBuf], formatter: Formatter) -> Result<()> {
55        match formatter {
56            Formatter::Prettier => Self::format_with_prettier(files),
57            Formatter::Biome => Self::format_with_biome(files),
58        }
59    }
60
61    fn format_with_prettier(files: &[std::path::PathBuf]) -> Result<()> {
62        let file_paths: Vec<String> = files
63            .iter()
64            .filter_map(|p| p.to_str().map(|s| s.to_string()))
65            .collect();
66
67        if file_paths.is_empty() {
68            return Ok(());
69        }
70
71        // Use glob pattern if we have many files to avoid command line length issues
72        let output = if file_paths.len() > 50 {
73            // Use glob pattern instead
74            Command::new("npx")
75                .arg("prettier")
76                .arg("--write")
77                .arg("src/**/*.ts")
78                .output()
79        } else {
80            Command::new("npx")
81                .arg("prettier")
82                .arg("--write")
83                .args(&file_paths)
84                .output()
85        };
86
87        match output {
88            Ok(output) => {
89                if !output.status.success() {
90                    let stderr = String::from_utf8_lossy(&output.stderr);
91                    let stdout = String::from_utf8_lossy(&output.stdout);
92                    eprintln!("Warning: Prettier exited with error:");
93                    eprintln!("  stderr: {}", stderr);
94                    if !stdout.is_empty() {
95                        eprintln!("  stdout: {}", stdout);
96                    }
97                }
98                Ok(())
99            }
100            Err(e) => {
101                // Silently fail if prettier is not available
102                eprintln!("Warning: Failed to run prettier: {}", e);
103                Ok(())
104            }
105        }
106    }
107
108    fn format_with_biome(files: &[std::path::PathBuf]) -> Result<()> {
109        let file_paths: Vec<String> = files
110            .iter()
111            .filter_map(|p| p.to_str().map(|s| s.to_string()))
112            .collect();
113
114        if file_paths.is_empty() {
115            return Ok(());
116        }
117
118        let output = Command::new("npx")
119            .arg("@biomejs/biome")
120            .arg("format")
121            .arg("--write")
122            .args(&file_paths)
123            .output();
124
125        match output {
126            Ok(_) => Ok(()),
127            Err(e) => {
128                // Silently fail if biome is not available
129                eprintln!("Warning: Failed to run biome: {}", e);
130                Ok(())
131            }
132        }
133    }
134}