gitai/features/changelog/
releasenotes.rs1use super::common::generate_changes_content;
2use super::models::{
3 BreakingChange, ChangeMetrics, Highlight, ReleaseNotesResponse, Section, SectionItem,
4};
5use super::prompt;
6use crate::common::DetailLevel;
7use crate::config::Config;
8use crate::git::GitRepo;
9use anyhow::Result;
10use colored::Colorize;
11use std::fmt::Write as FmtWrite;
12use std::sync::Arc;
13
14pub struct ReleaseNotesGenerator;
16
17impl ReleaseNotesGenerator {
18 pub async fn generate(
33 git_repo: Arc<GitRepo>,
34 from: &str,
35 to: &str,
36 config: &Config,
37 detail_level: DetailLevel,
38 version_name: Option<String>,
39 ) -> Result<String> {
40 let release_notes: ReleaseNotesResponse = generate_changes_content::<ReleaseNotesResponse>(
41 git_repo,
42 from,
43 to,
44 config,
45 detail_level,
46 prompt::create_release_notes_system_prompt,
47 prompt::create_release_notes_user_prompt,
48 )
49 .await?;
50
51 Ok(format_release_notes_response(
52 &release_notes,
53 version_name.as_deref(),
54 ))
55 }
56}
57
58fn format_release_notes_response(
60 response: &ReleaseNotesResponse,
61 version_name: Option<&str>,
62) -> String {
63 let mut formatted = String::new();
64
65 let version = match version_name {
67 Some(name) => name.to_string(),
68 None => response.version.clone().unwrap_or_default(),
69 };
70
71 write!(
72 formatted,
73 "# Release Notes - v{}\n\n",
74 version.bright_green().bold()
75 )
76 .expect("writing to string should never fail");
77 write!(
78 formatted,
79 "Release Date: {}\n\n",
80 response.release_date.clone().unwrap_or_default().yellow()
81 )
82 .expect("writing to string should never fail");
83
84 write!(formatted, "{}\n\n", response.summary.bright_cyan())
86 .expect("writing to string should never fail");
87
88 if !response.highlights.is_empty() {
90 formatted.push_str(&"## ✨ Highlights\n\n".bright_magenta().bold().to_string());
91 for highlight in &response.highlights {
92 formatted.push_str(&format_highlight(highlight));
93 }
94 }
95
96 for section in &response.sections {
98 formatted.push_str(&format_section(section));
99 }
100
101 if !response.breaking_changes.is_empty() {
103 formatted.push_str(&"## ⚠️ Breaking Changes\n\n".bright_red().bold().to_string());
104 for breaking_change in &response.breaking_changes {
105 formatted.push_str(&format_breaking_change(breaking_change));
106 }
107 }
108
109 if !response.upgrade_notes.is_empty() {
111 formatted.push_str(&"## 🔧 Upgrade Notes\n\n".yellow().bold().to_string());
112 for note in &response.upgrade_notes {
113 writeln!(formatted, "- {note}").expect("writing to string should never fail");
114 }
115 formatted.push('\n');
116 }
117
118 formatted.push_str(&"## 📊 Metrics\n\n".bright_blue().bold().to_string());
120 formatted.push_str(&format_metrics(&response.metrics));
121
122 formatted
123}
124
125fn format_highlight(highlight: &Highlight) -> String {
127 format!(
128 "### {}\n\n{}\n\n",
129 highlight.title.bright_yellow().bold(),
130 highlight.description
131 )
132}
133
134fn format_section(section: &Section) -> String {
136 let mut formatted = format!("## {}\n\n", section.title.bright_blue().bold());
137 for item in §ion.items {
138 formatted.push_str(&format_section_item(item));
139 }
140 formatted.push('\n');
141 formatted
142}
143
144fn format_section_item(item: &SectionItem) -> String {
146 let mut formatted = format!("- {}", item.description);
147
148 if !item.associated_issues.is_empty() {
149 write!(
150 formatted,
151 " ({})",
152 item.associated_issues.join(", ").yellow()
153 )
154 .expect("writing to string should never fail");
155 }
156
157 if let Some(pr) = &item.pull_request {
158 write!(formatted, " [{}]", pr.bright_purple())
159 .expect("writing to string should never fail");
160 }
161
162 formatted.push('\n');
163 formatted
164}
165
166fn format_breaking_change(breaking_change: &BreakingChange) -> String {
168 format!(
169 "- {} ({})\n",
170 breaking_change.description,
171 breaking_change.commit_hash.dimmed()
172 )
173}
174
175fn format_metrics(metrics: &ChangeMetrics) -> String {
177 format!(
178 "- Total Commits: {}\n- Files Changed: {}\n- Insertions: {}\n- Deletions: {}\n",
179 metrics.total_commits.to_string().green(),
180 metrics.files_changed.to_string().yellow(),
181 metrics.insertions.to_string().green(),
182 metrics.deletions.to_string().red()
183 )
184}