use anyhow::{bail, Context, Result};
use sha2::{Sha256, Digest};
use std::fs;
use std::path::Path;
use std::process::{Command, Stdio};
use crate::converter::format;
use crate::utils::config::{self, BendisConfig};
pub fn run() -> Result<()> {
println!("Updating dependencies...");
let cfg = BendisConfig::load()?;
let bendis_dir = config::get_bendis_dir();
let root_dir = config::get_root_dir();
if !bendis_dir.exists() {
bail!(
"error: .bendis directory not found\nrun `bendis init` first to initialize the project"
);
}
println!(" Preparing cache files...");
run_bender_update_in_bendis(&bendis_dir, cfg.silent_mode == 1)?;
println!(" Converting URLs...");
format::convert(&bendis_dir, &root_dir)?;
println!(" Checking for changes...");
let needs_update = check_files_changed(&root_dir)?;
if needs_update {
println!(" Detected changes, updating dependencies...");
cleanup_root_files(&root_dir)?;
run_bender_update_in_root()?;
} else {
println!(" No changes detected, skipping bender update");
}
println!(" Cleaning up cache...");
cleanup_bendis_bender_dir(&bendis_dir, cfg.storage_saving_mode == 1)?;
verify_completion(&root_dir)?;
println!("Done");
Ok(())
}
fn calculate_file_hash(path: &Path) -> Result<String> {
let content = fs::read(path)
.with_context(|| format!("Failed to read {}", path.display()))?;
let hash = Sha256::digest(&content);
Ok(format!("{:x}", hash))
}
fn check_files_changed(root_dir: &Path) -> Result<bool> {
let bender_yml = root_dir.join("Bender.yml");
let dot_bender_yml = root_dir.join(".bender.yml");
if !bender_yml.exists() || !dot_bender_yml.exists() {
return Ok(true);
}
let current_bender_yml_hash = calculate_file_hash(&bender_yml)?;
let current_dot_bender_yml_hash = calculate_file_hash(&dot_bender_yml)?;
let bendis_dir = config::get_bendis_dir();
let new_bender_yml = bendis_dir.join("Bender.yml");
let new_dot_bender_yml = root_dir.join(".bender.yml");
if !new_bender_yml.exists() {
return Ok(true);
}
let new_bender_yml_hash = calculate_file_hash(&new_bender_yml)?;
let new_dot_bender_yml_hash = calculate_file_hash(&new_dot_bender_yml)?;
Ok(current_bender_yml_hash != new_bender_yml_hash ||
current_dot_bender_yml_hash != new_dot_bender_yml_hash)
}
fn run_bender_update_in_bendis(bendis_dir: &Path, silent: bool) -> Result<()> {
let mut cmd = Command::new("bender");
cmd.args(&["-d", "./.bendis", "update"]);
if silent {
cmd.stdout(Stdio::null());
cmd.stderr(Stdio::null());
}
let status = cmd.status()
.context("Failed to run bender. Is bender installed and in PATH?")?;
if !status.success() {
bail!("error: bender update in .bendis/ failed");
}
let lock_file = bendis_dir.join("Bender.lock");
if !lock_file.exists() {
bail!("error: failed to generate .bendis/Bender.lock");
}
Ok(())
}
fn cleanup_root_files(root_dir: &Path) -> Result<()> {
let old_lock = root_dir.join("Bender.lock");
if old_lock.exists() {
fs::remove_file(&old_lock)
.context("Failed to remove old Bender.lock")?;
}
let old_bender_dir = root_dir.join(".bender");
if old_bender_dir.exists() {
fs::remove_dir_all(&old_bender_dir)
.context("Failed to remove old .bender/ directory")?;
}
Ok(())
}
fn run_bender_update_in_root() -> Result<()> {
let status = Command::new("bender")
.arg("update")
.status()
.context("Failed to run bender in root directory")?;
if !status.success() {
bail!("error: bender update in root directory failed");
}
Ok(())
}
fn cleanup_bendis_bender_dir(bendis_dir: &Path, full_cleanup: bool) -> Result<()> {
if full_cleanup {
let bender_dir = bendis_dir.join(".bender");
if bender_dir.exists() {
fs::remove_dir_all(&bender_dir)
.context("Failed to remove .bendis/.bender/")?;
}
} else {
}
Ok(())
}
fn verify_completion(root_dir: &Path) -> Result<()> {
let mut missing = Vec::new();
if !root_dir.join("Bender.yml").exists() {
missing.push("Bender.yml");
}
if !root_dir.join(".bender.yml").exists() {
missing.push(".bender.yml");
}
if !root_dir.join("Bender.lock").exists() {
missing.push("Bender.lock");
}
if !root_dir.join(".bender").exists() {
missing.push(".bender/");
}
if !missing.is_empty() {
bail!("error: verification failed, missing: {}", missing.join(", "));
}
Ok(())
}