use crate::config::{Config, ConfigError};
use crate::generator::{self, GeneratorError};
use crate::migrations::{self, Migration, MigrationError};
use crate::templates::{self, TemplateError};
use crate::verify::{self, VerifyError, VerifyReport};
use semver::Version;
use std::path::{Path, PathBuf};
use thiserror::Error;
#[derive(Debug)]
pub struct UpgradeResult {
pub old_version: String,
pub new_version: String,
pub files_generated: Vec<PathBuf>,
pub migrations: Vec<Migration>,
pub verification: VerifyReport,
}
#[derive(Debug, Error)]
pub enum UpgradeError {
#[error("Not a Rust crate: Cargo.toml not found in target directory")]
NotRustCrate,
#[error("Not a git repository: .git/ directory not found")]
NotGitRepo,
#[error(
"Not initialized: rust-bucket.toml not found. Use 'rust-bucket apply' to initialize first."
)]
NotInitialized,
#[error("Configuration error: {0}")]
ConfigError(#[from] ConfigError),
#[error("Generator error: {0}")]
GeneratorError(#[from] GeneratorError),
#[error("Verification error: {0}")]
VerifyError(#[from] VerifyError),
#[error("Template error: {0}")]
TemplateError(#[from] TemplateError),
#[error("Migration error: {0}")]
MigrationError(#[from] MigrationError),
#[error("Invalid version '{0}': {1}")]
VersionParse(String, semver::Error),
}
pub fn run_upgrade(target_dir: &Path) -> Result<UpgradeResult, UpgradeError> {
if !target_dir.join("Cargo.toml").exists() {
return Err(UpgradeError::NotRustCrate);
}
if !target_dir.join(".git").exists() {
return Err(UpgradeError::NotGitRepo);
}
if !generator::has_rust_bucket_toml(target_dir) {
return Err(UpgradeError::NotInitialized);
}
let config_path = target_dir.join("rust-bucket.toml");
let mut config = Config::load(&config_path)?;
let old_version_str = config.rust_bucket_version.clone();
let new_version_str = env!("CARGO_PKG_VERSION").to_string();
let old_version = Version::parse(&old_version_str)
.map_err(|e| UpgradeError::VersionParse(old_version_str.clone(), e))?;
let new_version = Version::parse(&new_version_str)
.map_err(|e| UpgradeError::VersionParse(new_version_str.clone(), e))?;
let migrations_list = migrations::migrations_between(&old_version, &new_version)?;
config.rust_bucket_version = new_version_str.clone();
config.save(&config_path)?;
let (_temp_dir, temp_path) = templates::extract_to_temp()?;
let mut files_generated = generator::render(&temp_path, target_dir, &config, true)?;
let claude_symlink = generator::create_claude_symlink(target_dir)?;
files_generated.push(claude_symlink);
let verification = verify::run_all(target_dir)?;
Ok(UpgradeResult {
old_version: old_version_str,
new_version: new_version_str,
files_generated,
migrations: migrations_list,
verification,
})
}
#[cfg(test)]
mod tests {
use super::*;
use tempfile::TempDir;
#[test]
fn test_upgrade_not_rust_crate() -> Result<(), Box<dyn std::error::Error>> {
let temp_dir = TempDir::new()?;
let result = run_upgrade(temp_dir.path());
assert!(matches!(result.unwrap_err(), UpgradeError::NotRustCrate));
Ok(())
}
#[test]
fn test_upgrade_not_git_repo() -> Result<(), Box<dyn std::error::Error>> {
let temp_dir = TempDir::new()?;
std::fs::write(
temp_dir.path().join("Cargo.toml"),
"[package]\nname = \"test\"",
)?;
let result = run_upgrade(temp_dir.path());
assert!(matches!(result.unwrap_err(), UpgradeError::NotGitRepo));
Ok(())
}
#[test]
fn test_upgrade_not_initialized() -> Result<(), Box<dyn std::error::Error>> {
let temp_dir = TempDir::new()?;
std::fs::write(
temp_dir.path().join("Cargo.toml"),
"[package]\nname = \"test\"",
)?;
std::fs::create_dir(temp_dir.path().join(".git"))?;
let result = run_upgrade(temp_dir.path());
assert!(matches!(result.unwrap_err(), UpgradeError::NotInitialized));
Ok(())
}
}