use anyhow::{Result, bail}; use colored::*; use std::path::PathBuf; use std::fs; pub fn upgrade_project(path: &str) -> Result<()> { let project_path = PathBuf::from(path); // Verify it's a weevil project (check for old .weevil-version or new .weevil.toml) let has_old_version = project_path.join(".weevil-version").exists(); let has_config = project_path.join(".weevil.toml").exists(); if !has_old_version && !has_config { bail!("Not a weevil project: {} (missing .weevil-version or .weevil.toml)", path); } println!("{}", format!("Upgrading project: {}", path).bright_yellow()); println!(); // Get SDK config let sdk_config = crate::sdk::SdkConfig::new()?; // Ensure FTC SDK has local.properties (in case it was installed before this feature) ensure_local_properties(&sdk_config)?; // Load or create project config let project_config = if has_config { println!("Found existing .weevil.toml"); crate::project::ProjectConfig::load(&project_path)? } else { println!("Creating .weevil.toml (migrating from old format)"); let project_name = project_path.file_name() .and_then(|n| n.to_str()) .unwrap_or("unknown"); crate::project::ProjectConfig::new(project_name, sdk_config.ftc_sdk_path.clone(), sdk_config.android_sdk_path.clone())? }; println!("Current SDK: {}", project_config.ftc_sdk_path.display()); println!("SDK Version: {}", project_config.ftc_sdk_version); println!(); // Files that are safe to overwrite (infrastructure, not user code) let safe_to_overwrite = vec![ "build.gradle.kts", "settings.gradle.kts", "build.sh", "build.bat", "deploy.sh", "deploy.bat", "gradlew", "gradlew.bat", "gradle/wrapper/gradle-wrapper.properties", "gradle/wrapper/gradle-wrapper.jar", ".gitignore", // Android Studio integration — regenerated so run configs stay in // sync if deploy.sh flags or script names ever change. ".idea/workspace.xml", ".idea/runConfigurations/Build.xml", ".idea/runConfigurations/Build (Windows).xml", ".idea/runConfigurations/Deploy (auto).xml", ".idea/runConfigurations/Deploy (auto) (Windows).xml", ".idea/runConfigurations/Deploy (USB).xml", ".idea/runConfigurations/Deploy (USB) (Windows).xml", ".idea/runConfigurations/Deploy (WiFi).xml", ".idea/runConfigurations/Deploy (WiFi) (Windows).xml", ".idea/runConfigurations/Test.xml", ".idea/runConfigurations/Test (Windows).xml", ]; println!("{}", "Updating infrastructure files...".bright_yellow()); // Create a modified SDK config that uses the project's configured FTC SDK let project_sdk_config = crate::sdk::SdkConfig { ftc_sdk_path: project_config.ftc_sdk_path.clone(), android_sdk_path: sdk_config.android_sdk_path.clone(), cache_dir: sdk_config.cache_dir.clone(), }; // Regenerate safe files using the project's configured SDK let builder = crate::project::ProjectBuilder::new( &project_config.project_name, &project_sdk_config )?; // Temporarily create files in a temp location let temp_dir = tempfile::tempdir()?; builder.create(temp_dir.path(), &project_sdk_config)?; // Copy only the safe files for file in safe_to_overwrite { let src = temp_dir.path().join(file); let dst = project_path.join(file); if src.exists() { if let Some(parent) = dst.parent() { fs::create_dir_all(parent)?; } fs::copy(&src, &dst)?; println!(" {} {}", "✓".green(), file); // Make executable if needed #[cfg(unix)] if file.ends_with(".sh") || file == "gradlew" { use std::os::unix::fs::PermissionsExt; let mut perms = fs::metadata(&dst)?.permissions(); perms.set_mode(0o755); fs::set_permissions(&dst, perms)?; } } } // Save/update the config project_config.save(&project_path)?; println!(" {} {}", "✓".green(), ".weevil.toml"); // Remove old version marker if it exists let old_version_file = project_path.join(".weevil-version"); if old_version_file.exists() { fs::remove_file(old_version_file)?; println!(" {} {}", "✓".green(), "Removed old .weevil-version"); } println!(); println!("{}", "═══════════════════════════════════════════════════════════".bright_green()); println!("{}", " ✓ Project Upgraded!".bright_green().bold()); println!("{}", "═══════════════════════════════════════════════════════════".bright_green()); println!(); println!("{}", "Your code in src/ was preserved.".green()); println!("{}", "Build scripts and gradle configuration updated.".green()); println!(); println!("Test it: ./gradlew test"); println!(); Ok(()) } fn ensure_local_properties(sdk_config: &crate::sdk::SdkConfig) -> Result<()> { let local_properties_path = sdk_config.ftc_sdk_path.join("local.properties"); if !local_properties_path.exists() { println!("Creating local.properties in FTC SDK..."); let android_sdk_str = sdk_config.android_sdk_path .display() .to_string() .replace("\\", "/"); let local_properties = format!("sdk.dir={}\n", android_sdk_str); fs::write(&local_properties_path, local_properties)?; println!("{} Created local.properties", "✓".green()); } Ok(()) }