use std::path::{Path, PathBuf}; use anyhow::{Context, Result, bail}; use serde::Serialize; use crate::{ cli::CaptureCliGoldensArgs, workspace::{ capture_stdout_lines, default_binary_path, load_workspace_metadata, utc_now_rfc3339, write_utf8_lines, write_utf8_text, }, }; #[derive(Debug, Serialize)] /// JSON manifest written beside captured CLI compatibility goldens. struct CompatManifest { /// UTC generation timestamp for the manifest. generated_at_utc: String, /// Relative paths captured by the workflow. captured: Vec<&'static str>, } #[derive(Debug, Serialize)] /// Human-readable JSON summary printed after capture. struct CompatSummary { #[serde(rename = "Captured")] /// Relative paths captured by the workflow. captured: Vec<&'static str>, } /// Captures upstream and Rust CLI help/version output as compatibility goldens. pub fn run_capture_cli_goldens(args: &CaptureCliGoldensArgs) -> Result<()> { let metadata = load_workspace_metadata()?; let host_triple = crate::workspace::host_triple()?; let upstream_binary = args .upstream_binary .clone() .unwrap_or_else(default_upstream_binary); let rust_binary = args .rust_binary .clone() .unwrap_or_else(|| default_binary_path(&metadata, &host_triple, &host_triple)); let output_root = args.output_root.clone().unwrap_or_else(|| { metadata .workspace_root .join("docs/compatibility/goldens/cli") }); ensure_path_exists(&upstream_binary, "upstream binary")?; ensure_path_exists(&rust_binary, "rust binary")?; let upstream_dir = output_root.join("upstream"); let rust_dir = output_root.join("rust"); std::fs::create_dir_all(&upstream_dir) .with_context(|| format!("failed to create {}", upstream_dir.display()))?; std::fs::create_dir_all(&rust_dir) .with_context(|| format!("failed to create {}", rust_dir.display()))?; let upstream_version = capture_stdout_lines(&upstream_binary, &["-v"])?; let upstream_help = redact_windows_home_paths(capture_stdout_lines(&upstream_binary, &["--help=#all"])?); let rust_version = capture_stdout_lines(&rust_binary, &["--version"])?; let rust_help = redact_windows_home_paths(capture_stdout_lines(&rust_binary, &["--help=#all"])?); write_utf8_lines(&upstream_dir.join("version.txt"), &upstream_version)?; write_utf8_lines(&upstream_dir.join("help-all.txt"), &upstream_help)?; write_utf8_lines(&rust_dir.join("version.txt"), &rust_version)?; write_utf8_lines(&rust_dir.join("help-all.txt"), &rust_help)?; let captured = captured_paths(); let manifest = CompatManifest { generated_at_utc: utc_now_rfc3339()?, captured: captured.to_vec(), }; let manifest_text = serde_json::to_string_pretty(&manifest) .context("failed to serialize compatibility manifest")?; write_utf8_text(&output_root.join("manifest.json"), &(manifest_text + "\n"))?; let summary = CompatSummary { captured: summary_paths().to_vec(), }; println!( "{}", serde_json::to_string_pretty(&summary) .context("failed to serialize compatibility summary")? ); Ok(()) } /// Returns the repository-local default upstream aria2 binary path. fn default_upstream_binary() -> PathBuf { crate::workspace::repo_root() .join("dist") .join("upstream") .join("aria2-1.37.0-win-64bit-build1") .join("aria2-1.37.0-win-64bit-build1") .join("aria2c.exe") } /// Ensures an expected binary path exists. fn ensure_path_exists(path: &Path, label: &str) -> Result<()> { if path.is_file() { Ok(()) } else { bail!("{label} not found: {}", path.display()) } } /// Replaces a Windows account name embedded in captured CLI help text. fn redact_windows_home_paths(lines: Vec) -> Vec { lines .into_iter() .map(|line| redact_windows_home_path(&line)) .collect() } /// Replaces the first Windows home-directory account name in a line. fn redact_windows_home_path(line: &str) -> String { const PREFIX: &str = "C:/Users/"; let Some((leading, remainder)) = line.split_once(PREFIX) else { return line.to_owned(); }; let Some((_, trailing)) = remainder.split_once('/') else { return line.to_owned(); }; format!("{leading}{PREFIX}/{trailing}") } /// Returns the golden files produced by the capture workflow. const fn captured_paths() -> &'static [&'static str] { &[ "upstream/version.txt", "upstream/help-all.txt", "rust/version.txt", "rust/help-all.txt", ] } /// Returns all paths reported in the workflow summary. const fn summary_paths() -> &'static [&'static str] { &[ "upstream/version.txt", "upstream/help-all.txt", "rust/version.txt", "rust/help-all.txt", "manifest.json", ] } #[cfg(test)] mod tests { use super::redact_windows_home_path; #[test] fn redacts_a_windows_home_path_in_captured_help() { assert_eq!( redact_windows_home_path("Default: C:/Users/alice/.netrc"), "Default: C:/Users//.netrc" ); } }