provenant/
golden_maintenance.rs1use std::fs;
5use std::path::{Path, PathBuf};
6use std::process::Command;
7
8use anyhow::{Context, Result};
9
10fn prettier_parser_for_path(path: &Path) -> Option<&'static str> {
11 if path
12 .file_name()
13 .and_then(|name| name.to_str())
14 .is_some_and(|name| name.ends_with(".expected"))
15 {
16 Some("json")
17 } else {
18 None
19 }
20}
21
22pub fn find_files_with_extension(dir: &Path, extension: &str) -> Result<Vec<PathBuf>> {
23 let mut paths = Vec::new();
24 if !dir.is_dir() {
25 return Ok(paths);
26 }
27
28 fn recurse(dir: &Path, extension: &str, out: &mut Vec<PathBuf>) -> Result<()> {
29 for entry in fs::read_dir(dir)? {
30 let entry = entry?;
31 let path = entry.path();
32 if path.is_dir() {
33 recurse(&path, extension, out)?;
34 } else if path
35 .extension()
36 .and_then(|ext| ext.to_str())
37 .is_some_and(|ext| ext == extension)
38 {
39 out.push(path);
40 }
41 }
42 Ok(())
43 }
44
45 recurse(dir, extension, &mut paths)?;
46 paths.sort();
47 Ok(paths)
48}
49
50pub fn run_prettier(paths: &[PathBuf]) -> Result<()> {
51 if paths.is_empty() {
52 return Ok(());
53 }
54
55 let ignore_path = std::env::temp_dir().join("provenant-empty-prettierignore");
56 if !ignore_path.exists() {
57 fs::write(&ignore_path, "").context("failed to create empty prettier ignore file")?;
58 }
59
60 const CHUNK_SIZE: usize = 100;
61
62 let mut default_paths = Vec::new();
63 let mut json_paths = Vec::new();
64
65 for path in paths {
66 match prettier_parser_for_path(path) {
67 Some("json") => json_paths.push(path.clone()),
68 _ => default_paths.push(path.clone()),
69 }
70 }
71
72 for (parser, parser_paths) in [(None, default_paths), (Some("json"), json_paths)] {
73 for chunk in parser_paths.chunks(CHUNK_SIZE) {
74 let mut cmd = Command::new("npm");
75 cmd.args([
76 "exec",
77 "--",
78 "prettier",
79 "--write",
80 "--ignore-path",
81 ignore_path
82 .to_str()
83 .context("temporary prettier ignore path is not valid UTF-8")?,
84 ]);
85 if let Some(parser) = parser {
86 cmd.args(["--parser", parser]);
87 }
88 for path in chunk {
89 cmd.arg(path);
90 }
91
92 let output = cmd
93 .output()
94 .context("failed to run `npm exec -- prettier --write`")?;
95
96 if !output.status.success() {
97 let stderr = String::from_utf8_lossy(&output.stderr);
98 anyhow::bail!(
99 "prettier formatting failed (status: {}): {}",
100 output.status,
101 stderr.trim()
102 );
103 }
104 }
105 }
106
107 Ok(())
108}
109
110#[cfg(test)]
111mod tests {
112 use super::prettier_parser_for_path;
113 use std::path::Path;
114
115 #[test]
116 fn test_prettier_parser_for_expected_fixture_without_extension() {
117 assert_eq!(
118 prettier_parser_for_path(Path::new("testdata/maven-golden/basic/pom.xml.expected")),
119 Some("json")
120 );
121 }
122
123 #[test]
124 fn test_prettier_parser_for_regular_json_extension_is_inferred() {
125 assert_eq!(
126 prettier_parser_for_path(Path::new("testdata/foo/bar.expected.json")),
127 None
128 );
129 }
130
131 #[test]
132 fn test_prettier_parser_for_markdown_stays_default() {
133 assert_eq!(
134 prettier_parser_for_path(Path::new("docs/SUPPORTED_FORMATS.md")),
135 None
136 );
137 }
138}