1use anyhow::Result;
4use base64::{engine::general_purpose::STANDARD as BASE64, Engine};
5use clap::Args as ClapArgs;
6use colored::Colorize;
7use std::path::Path;
8
9use crate::core::{ConfigManager, Git, NotesManager, ARTIFACTS_NOTES_REF};
10
11#[derive(ClapArgs, Debug)]
13pub struct Args {
14 pub adr_id: String,
16
17 pub file: String,
19
20 #[arg(long)]
22 pub name: Option<String>,
23
24 #[arg(long)]
26 pub description: Option<String>,
27}
28
29pub fn run(args: Args) -> Result<()> {
35 let git = Git::new();
36 git.check_repository()?;
37
38 let config = ConfigManager::new(git.clone()).load()?;
39 let notes = NotesManager::new(git.clone(), config);
40
41 let adrs = notes.list()?;
43 let adr = adrs
44 .into_iter()
45 .find(|a| a.id == args.adr_id || a.id.contains(&args.adr_id))
46 .ok_or_else(|| anyhow::anyhow!("ADR not found: {}", args.adr_id))?;
47
48 let file_path = Path::new(&args.file);
50 if !file_path.exists() {
51 anyhow::bail!("File not found: {}", args.file);
52 }
53
54 let filename = args.name.clone().unwrap_or_else(|| {
56 file_path.file_name().map_or_else(
57 || "attachment".to_string(),
58 |s| s.to_string_lossy().to_string(),
59 )
60 });
61
62 eprintln!(
63 "{} Attaching {} to ADR {}",
64 "→".blue(),
65 filename.cyan(),
66 adr.id.cyan()
67 );
68
69 let content = std::fs::read(file_path)?;
71 let encoded = BASE64.encode(&content);
72
73 let metadata = std::fs::metadata(file_path)?;
75 let size = metadata.len();
76
77 let artifact = serde_json::json!({
79 "filename": filename,
80 "size": size,
81 "adr_id": adr.id,
82 "description": args.description,
83 "content": encoded,
84 });
85
86 let artifact_content = serde_json::to_string_pretty(&artifact)?;
89
90 git.notes_add(ARTIFACTS_NOTES_REF, &adr.commit, &artifact_content)?;
91
92 eprintln!(
93 "{} Attached {} ({} bytes) to ADR {}",
94 "✓".green(),
95 filename.cyan(),
96 size,
97 adr.id.cyan()
98 );
99
100 Ok(())
101}