use anyhow::{Result, Context}; use colored::*; use std::path::PathBuf; use std::fs; use crate::project::config::ProjectConfig; use crate::templates::{TemplateManager, TemplateContext}; use crate::version::ANVIL_VERSION; /// Files that anvil owns and can safely refresh. /// These are build/deploy infrastructure -- not user source code. const REFRESHABLE_FILES: &[&str] = &[ "build.sh", "build.bat", "upload.sh", "upload.bat", "monitor.sh", "monitor.bat", "_detect_port.ps1", "test/run_tests.sh", "test/run_tests.bat", ]; pub fn run_refresh(project_dir: Option<&str>, force: bool) -> Result<()> { // Resolve project directory let project_path = match project_dir { Some(dir) => PathBuf::from(dir), None => std::env::current_dir() .context("Could not determine current directory")?, }; let project_root = ProjectConfig::find_project_root(&project_path)?; let config = ProjectConfig::load(&project_root)?; println!( "Refreshing project: {}", config.project.name.bright_white().bold() ); println!( "Project directory: {}", project_root.display().to_string().bright_black() ); println!(); // Generate fresh copies of all refreshable files from the template let template_name = "basic"; let context = TemplateContext { project_name: config.project.name.clone(), anvil_version: ANVIL_VERSION.to_string(), fqbn: config.build.fqbn.clone(), baud: config.monitor.baud, }; // Extract template into a temp directory so we can compare let temp_dir = tempfile::tempdir() .context("Failed to create temp directory")?; TemplateManager::extract(template_name, temp_dir.path(), &context)?; // Compare each refreshable file let mut up_to_date = Vec::new(); let mut will_create = Vec::new(); let mut has_changes = Vec::new(); for &filename in REFRESHABLE_FILES { let existing = project_root.join(filename); let fresh = temp_dir.path().join(filename); if !fresh.exists() { // Template doesn't produce this file (shouldn't happen) continue; } let fresh_content = fs::read(&fresh) .context(format!("Failed to read template file: {}", filename))?; if !existing.exists() { will_create.push(filename); continue; } let existing_content = fs::read(&existing) .context(format!("Failed to read project file: {}", filename))?; if existing_content == fresh_content { up_to_date.push(filename); } else { has_changes.push(filename); } } // Report status if !up_to_date.is_empty() { println!( "{} {} file(s) already up to date", "ok".green(), up_to_date.len() ); } if !will_create.is_empty() { for f in &will_create { println!(" {} {} (new)", "+".bright_green(), f.bright_white()); } } if !has_changes.is_empty() { for f in &has_changes { println!( " {} {} (differs from latest)", "~".bright_yellow(), f.bright_white() ); } } // Decide what to do if has_changes.is_empty() && will_create.is_empty() { println!(); println!( "{}", "All scripts are up to date. Nothing to do." .bright_green() .bold() ); return Ok(()); } if !has_changes.is_empty() && !force { println!(); println!( "{} {} script(s) differ from the latest Anvil templates.", "!".bright_yellow(), has_changes.len() ); println!( "This is normal after upgrading Anvil. To update them, run:" ); println!(); println!(" {}", "anvil refresh --force".bright_cyan()); println!(); println!( " {}", "Only build scripts are replaced. Your .anvil.toml and source code are never touched." .bright_black() ); return Ok(()); } // Apply updates let files_to_write: Vec<&str> = if force { will_create.iter().chain(has_changes.iter()).copied().collect() } else { will_create.to_vec() }; for filename in &files_to_write { let fresh = temp_dir.path().join(filename); let dest = project_root.join(filename); // Ensure parent directory exists if let Some(parent) = dest.parent() { fs::create_dir_all(parent)?; } fs::copy(&fresh, &dest) .context(format!("Failed to write: {}", filename))?; } // Make shell scripts executable on Unix #[cfg(unix)] { use std::os::unix::fs::PermissionsExt; for filename in &files_to_write { if filename.ends_with(".sh") { let path = project_root.join(filename); if let Ok(meta) = fs::metadata(&path) { let mut perms = meta.permissions(); perms.set_mode(0o755); let _ = fs::set_permissions(&path, perms); } } } } println!(); println!( "{} Updated {} file(s).", "ok".green(), files_to_write.len() ); Ok(()) }