Skip to main content

provenant/
golden_maintenance.rs

1// SPDX-FileCopyrightText: Provenant contributors
2// SPDX-License-Identifier: Apache-2.0
3
4use 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}