use anyhow::Result;
use chrono::Utc;
use colored::Colorize;
use std::fs;
use super::db::{self, DEFAULT_TAP_NAME};
use super::models::InstalledSkill;
use crate::paths::get_skills_install_dir;
use crate::skill::discover_skills;
pub fn migrate_old_installations() -> Result<()> {
let install_dir = get_skills_install_dir()?;
if !install_dir.exists() {
return Ok(());
}
let old_skills = discover_skills(&install_dir)?;
if old_skills.is_empty() {
return Ok(());
}
println!(
"{} Found {} old-style installation(s), migrating...",
"=>".green().bold(),
old_skills.len()
);
let mut db = db::init_db()?;
let new_tap_dir = install_dir.join(DEFAULT_TAP_NAME);
fs::create_dir_all(&new_tap_dir)?;
for skill in old_skills {
let old_path = &skill.path;
let new_path = new_tap_dir.join(&skill.name);
let full_name = format!("{}/{}", DEFAULT_TAP_NAME, skill.name);
if old_path.parent() == Some(&new_tap_dir) {
continue;
}
if is_tap_directory(old_path) {
continue;
}
if new_path.exists() {
println!(" {} {} (already exists at new location)", "○".yellow(), skill.name);
fs::remove_dir_all(old_path)?;
} else {
fs::rename(old_path, &new_path)?;
println!(" {} {} (migrated)", "✓".green(), skill.name);
}
if !db::is_skill_installed(&db, &full_name) {
let installed = InstalledSkill {
tap: DEFAULT_TAP_NAME.to_string(),
skill: skill.name.clone(),
commit: None,
installed_at: Utc::now(),
local: true,
source_url: None,
source_path: None,
};
db::add_installed_skill(&mut db, &full_name, installed);
}
}
db::save_db(&db)?;
println!("{} Migration complete!", "Done!".green().bold());
Ok(())
}
fn is_tap_directory(path: &std::path::Path) -> bool {
if let Ok(entries) = fs::read_dir(path) {
for entry in entries.flatten() {
let entry_path = entry.path();
if entry_path.is_dir() {
if entry_path.join("SKILL.md").exists() {
return true;
}
}
}
}
false
}
pub fn needs_migration() -> Result<bool> {
let install_dir = get_skills_install_dir()?;
if !install_dir.exists() {
return Ok(false);
}
if let Ok(entries) = fs::read_dir(&install_dir) {
for entry in entries.flatten() {
let path = entry.path();
if path.is_dir() {
if path.join("SKILL.md").exists() {
return Ok(true);
}
}
}
}
Ok(false)
}
#[cfg(test)]
mod tests {
use super::*;
use std::fs;
use tempfile::TempDir;
#[test]
fn test_is_tap_directory_empty() {
let dir = TempDir::new().unwrap();
assert!(!is_tap_directory(dir.path()));
}
#[test]
fn test_is_tap_directory_with_skill() {
let dir = TempDir::new().unwrap();
let skill_dir = dir.path().join("my-skill");
fs::create_dir(&skill_dir).unwrap();
fs::write(skill_dir.join("SKILL.md"), "---\nname: test\n---").unwrap();
assert!(is_tap_directory(dir.path()));
}
#[test]
fn test_is_tap_directory_without_skill() {
let dir = TempDir::new().unwrap();
let subdir = dir.path().join("some-dir");
fs::create_dir(&subdir).unwrap();
fs::write(subdir.join("README.md"), "hello").unwrap();
assert!(!is_tap_directory(dir.path()));
}
}