feat:(first commit)created repository and complete 0.1.0
This commit is contained in:
199
src/commands/changelog.rs
Normal file
199
src/commands/changelog.rs
Normal file
@@ -0,0 +1,199 @@
|
||||
use anyhow::{bail, Result};
|
||||
use chrono::Utc;
|
||||
use clap::Parser;
|
||||
use colored::Colorize;
|
||||
use dialoguer::{Confirm, Input};
|
||||
use std::path::PathBuf;
|
||||
|
||||
use crate::config::manager::ConfigManager;
|
||||
use crate::generator::ContentGenerator;
|
||||
use crate::git::find_repo;
|
||||
use crate::git::{changelog::*, CommitInfo, GitRepo};
|
||||
|
||||
/// Generate changelog
|
||||
#[derive(Parser)]
|
||||
pub struct ChangelogCommand {
|
||||
/// Output file path
|
||||
#[arg(short, long)]
|
||||
output: Option<PathBuf>,
|
||||
|
||||
/// Version to generate changelog for
|
||||
#[arg(short, long)]
|
||||
version: Option<String>,
|
||||
|
||||
/// Generate from specific tag
|
||||
#[arg(short, long)]
|
||||
from: Option<String>,
|
||||
|
||||
/// Generate to specific ref
|
||||
#[arg(short, long, default_value = "HEAD")]
|
||||
to: String,
|
||||
|
||||
/// Initialize new changelog file
|
||||
#[arg(short, long)]
|
||||
init: bool,
|
||||
|
||||
/// Generate with AI
|
||||
#[arg(short, long)]
|
||||
generate: bool,
|
||||
|
||||
/// Prepend to existing changelog
|
||||
#[arg(short, long)]
|
||||
prepend: bool,
|
||||
|
||||
/// Include commit hashes
|
||||
#[arg(long)]
|
||||
include_hashes: bool,
|
||||
|
||||
/// Include authors
|
||||
#[arg(long)]
|
||||
include_authors: bool,
|
||||
|
||||
/// Format (keep-a-changelog, github-releases)
|
||||
#[arg(short, long)]
|
||||
format: Option<String>,
|
||||
|
||||
/// Dry run (output to stdout)
|
||||
#[arg(long)]
|
||||
dry_run: bool,
|
||||
|
||||
/// Skip interactive prompts
|
||||
#[arg(short = 'y', long)]
|
||||
yes: bool,
|
||||
}
|
||||
|
||||
impl ChangelogCommand {
|
||||
pub async fn execute(&self) -> Result<()> {
|
||||
let repo = find_repo(".")?;
|
||||
let manager = ConfigManager::new()?;
|
||||
let config = manager.config();
|
||||
|
||||
// Initialize changelog if requested
|
||||
if self.init {
|
||||
let path = self.output.as_ref()
|
||||
.map(|p| p.clone())
|
||||
.unwrap_or_else(|| PathBuf::from(&config.changelog.path));
|
||||
|
||||
init_changelog(&path)?;
|
||||
println!("{} Initialized changelog at {:?}", "✓".green(), path);
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// Determine output path
|
||||
let output_path = self.output.as_ref()
|
||||
.map(|p| p.clone())
|
||||
.unwrap_or_else(|| PathBuf::from(&config.changelog.path));
|
||||
|
||||
// Determine format
|
||||
let format = match self.format.as_deref() {
|
||||
Some("github") | Some("github-releases") => ChangelogFormat::GitHubReleases,
|
||||
Some("keep") | Some("keep-a-changelog") => ChangelogFormat::KeepAChangelog,
|
||||
Some("custom") => ChangelogFormat::Custom,
|
||||
None => ChangelogFormat::KeepAChangelog,
|
||||
Some(f) => bail!("Unknown format: {}. Use: keep-a-changelog, github-releases", f),
|
||||
};
|
||||
|
||||
// Get version
|
||||
let version = if let Some(ref v) = self.version {
|
||||
v.clone()
|
||||
} else if !self.yes {
|
||||
Input::new()
|
||||
.with_prompt("Version")
|
||||
.default("Unreleased".to_string())
|
||||
.interact_text()?
|
||||
} else {
|
||||
"Unreleased".to_string()
|
||||
};
|
||||
|
||||
// Get commits
|
||||
println!("{} Fetching commits...", "→".blue());
|
||||
let commits = generate_from_history(&repo, self.from.as_deref(), Some(&self.to))?;
|
||||
|
||||
if commits.is_empty() {
|
||||
bail!("No commits found in the specified range");
|
||||
}
|
||||
|
||||
println!("{} Found {} commits", "✓".green(), commits.len());
|
||||
|
||||
// Generate changelog
|
||||
let changelog = if self.generate || (config.changelog.auto_generate && !self.yes) {
|
||||
self.generate_with_ai(&repo, &version, &commits).await?
|
||||
} else {
|
||||
self.generate_with_template(format, &version, &commits)?
|
||||
};
|
||||
|
||||
// Output or write
|
||||
if self.dry_run {
|
||||
println!("\n{}", "─".repeat(60));
|
||||
println!("{}", changelog);
|
||||
println!("{}", "─".repeat(60));
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// Preview
|
||||
if !self.yes {
|
||||
println!("\n{}", "─".repeat(60));
|
||||
println!("{}", "Changelog preview:".bold());
|
||||
println!("{}", "─".repeat(60));
|
||||
// Show first 20 lines
|
||||
let preview: String = changelog.lines().take(20).collect::<Vec<_>>().join("\n");
|
||||
println!("{}", preview);
|
||||
if changelog.lines().count() > 20 {
|
||||
println!("\n... ({} more lines)", changelog.lines().count() - 20);
|
||||
}
|
||||
println!("{}", "─".repeat(60));
|
||||
|
||||
let confirm = Confirm::new()
|
||||
.with_prompt(&format!("Write to {:?}?", output_path))
|
||||
.default(true)
|
||||
.interact()?;
|
||||
|
||||
if !confirm {
|
||||
println!("{}", "Cancelled.".yellow());
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
|
||||
// Write to file
|
||||
if self.prepend && output_path.exists() {
|
||||
let existing = std::fs::read_to_string(&output_path)?;
|
||||
let new_content = format!("{}\n{}", changelog, existing);
|
||||
std::fs::write(&output_path, new_content)?;
|
||||
} else {
|
||||
std::fs::write(&output_path, changelog)?;
|
||||
}
|
||||
|
||||
println!("{} Changelog written to {:?}", "✓".green(), output_path);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn generate_with_ai(
|
||||
&self,
|
||||
repo: &GitRepo,
|
||||
version: &str,
|
||||
commits: &[CommitInfo],
|
||||
) -> Result<String> {
|
||||
let manager = ConfigManager::new()?;
|
||||
let config = manager.config();
|
||||
|
||||
println!("{} AI is generating changelog...", "🤖");
|
||||
|
||||
let generator = ContentGenerator::new(&config.llm).await?;
|
||||
generator.generate_changelog_entry(version, commits).await
|
||||
}
|
||||
|
||||
fn generate_with_template(
|
||||
&self,
|
||||
format: ChangelogFormat,
|
||||
version: &str,
|
||||
commits: &[CommitInfo],
|
||||
) -> Result<String> {
|
||||
let generator = ChangelogGenerator::new()
|
||||
.format(format)
|
||||
.include_hashes(self.include_hashes)
|
||||
.include_authors(self.include_authors);
|
||||
|
||||
generator.generate(version, Utc::now(), commits)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user