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> {
78 if !target_dir.join("Cargo.toml").exists() {
80 return Err(UpgradeError::NotRustCrate);
81 }
82
83 if !target_dir.join(".git").exists() {
85 return Err(UpgradeError::NotGitRepo);
86 }
87
88 if !generator::has_rust_bucket_toml(target_dir) {
90 return Err(UpgradeError::NotInitialized);
91 }
92
93 let config_path = target_dir.join("rust-bucket.toml");
95 let mut config = Config::load(&config_path)?;
96 let old_version_str = config.rust_bucket_version.clone();
97 let new_version_str = env!("CARGO_PKG_VERSION").to_string();
98
99 let old_version = Version::parse(&old_version_str)
101 .map_err(|e| UpgradeError::VersionParse(old_version_str.clone(), e))?;
102 let new_version = Version::parse(&new_version_str)
103 .map_err(|e| UpgradeError::VersionParse(new_version_str.clone(), e))?;
104
105 let migrations_list = migrations::migrations_between(&old_version, &new_version)?;
107
108 config.rust_bucket_version = new_version_str.clone();
110 config.save(&config_path)?;
111
112 let (_temp_dir, temp_path) = templates::extract_to_temp()?;
114 let mut files_generated = generator::render(&temp_path, target_dir, &config, true)?;
115
116 let claude_symlink = generator::create_claude_symlink(target_dir)?;
118 files_generated.push(claude_symlink);
119
120 let verification = verify::run_all(target_dir)?;
122
123 Ok(UpgradeResult {
124 old_version: old_version_str,
125 new_version: new_version_str,
126 files_generated,
127 migrations: migrations_list,
128 verification,
129 })
130}
131
132#[cfg(test)]
133mod tests {
134 use super::*;
135 use tempfile::TempDir;
136
137 #[test]
138 fn test_upgrade_not_rust_crate() {
139 let temp_dir = TempDir::new().unwrap();
140 let result = run_upgrade(temp_dir.path());
141 assert!(matches!(result.unwrap_err(), UpgradeError::NotRustCrate));
142 }
143
144 #[test]
145 fn test_upgrade_not_git_repo() {
146 let temp_dir = TempDir::new().unwrap();
147 std::fs::write(
148 temp_dir.path().join("Cargo.toml"),
149 "[package]\nname = \"test\"",
150 )
151 .unwrap();
152 let result = run_upgrade(temp_dir.path());
153 assert!(matches!(result.unwrap_err(), UpgradeError::NotGitRepo));
154 }
155
156 #[test]
157 fn test_upgrade_not_initialized() {
158 let temp_dir = TempDir::new().unwrap();
159 std::fs::write(
160 temp_dir.path().join("Cargo.toml"),
161 "[package]\nname = \"test\"",
162 )
163 .unwrap();
164 std::fs::create_dir(temp_dir.path().join(".git")).unwrap();
165 let result = run_upgrade(temp_dir.path());
166 assert!(matches!(result.unwrap_err(), UpgradeError::NotInitialized));
167 }
168}