feat:(first commit)created repository and complete 0.1.0

This commit is contained in:
2026-01-30 14:18:32 +08:00
commit 5d4156e5e0
36 changed files with 8686 additions and 0 deletions

199
src/commands/changelog.rs Normal file
View 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)
}
}