1use crate::config::{Config, ConfigError};
4use crate::generator::{self, GeneratorError};
5use crate::migrations::{self, Migration, MigrationError};
6use crate::templates::{self, TemplateError};
7use crate::verify::{self, VerifyError, VerifyReport};
8use semver::Version;
9use std::path::{Path, PathBuf};
10use thiserror::Error;
11
12#[derive(Debug)]
14pub struct UpgradeResult {
15 pub old_version: String,
16 pub new_version: String,
17 pub files_generated: Vec<PathBuf>,
18 pub migrations: Vec<Migration>,
19 pub verification: VerifyReport,
20}
21
22#[derive(Debug, Error)]
24pub enum UpgradeError {
25 #[error("Not a Rust crate: Cargo.toml not found in target directory")]
27 NotRustCrate,
28
29 #[error("Not a git repository: .git/ directory not found")]
31 NotGitRepo,
32
33 #[error(
35 "Not initialized: rust-bucket.toml not found. Use 'rust-bucket apply' to initialize first."
36 )]
37 NotInitialized,
38
39 #[error("Configuration error: {0}")]
41 ConfigError(#[from] ConfigError),
42
43 #[error("Generator error: {0}")]
45 GeneratorError(#[from] GeneratorError),
46
47 #[error("Verification error: {0}")]
49 VerifyError(#[from] VerifyError),
50
51 #[error("Template error: {0}")]
53 TemplateError(#[from] TemplateError),
54
55 #[error("Migration error: {0}")]
57 MigrationError(#[from] MigrationError),
58
59 #[error("Invalid version '{0}': {1}")]
61 VersionParse(String, semver::Error),
62}
63
64pub fn run_upgrade(target_dir: &Path) -> Result<UpgradeResult, UpgradeError> {
68 if !target_dir.join("Cargo.toml").exists() {
69 return Err(UpgradeError::NotRustCrate);
70 }
71
72 if !target_dir.join(".git").exists() {
73 return Err(UpgradeError::NotGitRepo);
74 }
75
76 if !generator::has_rust_bucket_toml(target_dir) {
77 return Err(UpgradeError::NotInitialized);
78 }
79
80 let config_path = target_dir.join("rust-bucket.toml");
81 let mut config = Config::load(&config_path)?;
82 let old_version_str = config.rust_bucket_version.clone();
83 let new_version_str = env!("CARGO_PKG_VERSION").to_string();
84
85 let old_version = Version::parse(&old_version_str)
86 .map_err(|e| UpgradeError::VersionParse(old_version_str.clone(), e))?;
87 let new_version = Version::parse(&new_version_str)
88 .map_err(|e| UpgradeError::VersionParse(new_version_str.clone(), e))?;
89
90 let migrations_list = migrations::migrations_between(&old_version, &new_version)?;
91
92 config.rust_bucket_version = new_version_str.clone();
93 config.save(&config_path)?;
94
95 let (_temp_dir, temp_path) = templates::extract_to_temp()?;
96 let mut files_generated = generator::render(&temp_path, target_dir, &config, true)?;
97
98 let claude_symlink = generator::create_claude_symlink(target_dir)?;
99 files_generated.push(claude_symlink);
100
101 let verification = verify::run_all(target_dir)?;
102
103 Ok(UpgradeResult {
104 old_version: old_version_str,
105 new_version: new_version_str,
106 files_generated,
107 migrations: migrations_list,
108 verification,
109 })
110}
111
112#[cfg(test)]
113mod tests {
114 use super::*;
115 use tempfile::TempDir;
116
117 #[test]
118 fn test_upgrade_not_rust_crate() -> Result<(), Box<dyn std::error::Error>> {
119 let temp_dir = TempDir::new()?;
120 let result = run_upgrade(temp_dir.path());
121 assert!(matches!(result.unwrap_err(), UpgradeError::NotRustCrate));
122 Ok(())
123 }
124
125 #[test]
126 fn test_upgrade_not_git_repo() -> Result<(), Box<dyn std::error::Error>> {
127 let temp_dir = TempDir::new()?;
128 std::fs::write(
129 temp_dir.path().join("Cargo.toml"),
130 "[package]\nname = \"test\"",
131 )?;
132 let result = run_upgrade(temp_dir.path());
133 assert!(matches!(result.unwrap_err(), UpgradeError::NotGitRepo));
134 Ok(())
135 }
136
137 #[test]
138 fn test_upgrade_not_initialized() -> Result<(), Box<dyn std::error::Error>> {
139 let temp_dir = TempDir::new()?;
140 std::fs::write(
141 temp_dir.path().join("Cargo.toml"),
142 "[package]\nname = \"test\"",
143 )?;
144 std::fs::create_dir(temp_dir.path().join(".git"))?;
145 let result = run_upgrade(temp_dir.path());
146 assert!(matches!(result.unwrap_err(), UpgradeError::NotInitialized));
147 Ok(())
148 }
149}