Projektordner im ⚙︎-Dialog änderbar: Registry-Eintrag neu zuordnen, autoMemoryDirectory + Edit-Permission ziehen auf den neuen Root um; gesperrt bei laufender Session

This commit is contained in:
marcus.hinz
2026-07-05 15:37:49 +02:00
parent 2075f9aa71
commit 0ebdaf0460
3 changed files with 94 additions and 0 deletions
+75
View File
@@ -559,6 +559,53 @@ fn create_project_full_in(
Ok(())
}
/// Verlegt den Projektordner: Registry-Eintrag auf den neuen Pfad; Verweise
/// auf den alten Root in der settings.json (autoMemoryDirectory,
/// Edit-Permission) ziehen mit. Verschoben wird nichts — der neue Ordner
/// muss existieren.
fn set_project_dir_in(paths: &Paths, name: &str, dir: &str) -> Result<(), String> {
check_name(name)?;
let new_dir = expand_home(paths, dir);
if !new_dir.is_dir() {
return Err(format!("Ordner nicht gefunden: {}", new_dir.display()));
}
let mut reg = load_registry(paths)?;
let old_dir = reg
.get(name)
.cloned()
.ok_or_else(|| format!("Projekt nicht registriert: {name}"))?;
if old_dir == new_dir {
return Ok(());
}
reg.insert(name.to_string(), new_dir.clone());
save_registry(paths, &reg)?;
let sp = settings_path(&new_dir);
if !sp.is_file() {
return Ok(()); // importierte Projekte ohne settings.json
}
let old_c = contract_home(paths, &old_dir);
let new_c = contract_home(paths, &new_dir);
let raw = fs::read_to_string(&sp).map_err(|e| format!("{}: {e}", sp.display()))?;
let mut v: serde_json::Value =
serde_json::from_str(&raw).map_err(|e| format!("{}: {e}", sp.display()))?;
if let Some(mem) = v["autoMemoryDirectory"].as_str() {
if let Some(rest) = mem.strip_prefix(&old_c) {
v["autoMemoryDirectory"] = serde_json::json!(format!("{new_c}{rest}"));
}
}
if let Some(allow) = v["permissions"]["allow"].as_array_mut() {
let old_edit = format!("Edit({old_c}/**)");
for e in allow.iter_mut() {
if e.as_str() == Some(&old_edit) {
*e = serde_json::json!(format!("Edit({new_c}/**)"));
}
}
}
let raw = serde_json::to_string_pretty(&v).map_err(|e| e.to_string())?;
fs::write(&sp, raw + "\n").map_err(|e| format!("{}: {e}", sp.display()))
}
/// Nimmt einen bestehenden Ordner als Projekt auf; der Name ist der Ordnername.
fn add_project_in(paths: &Paths, path: &str) -> Result<(), String> {
let dir = expand_home(paths, path);
@@ -1544,6 +1591,15 @@ fn remove_project(name: String) -> Result<(), String> {
unregister_project(&Paths::real(), &name)
}
/// Projektordner neu zuordnen; bei laufender Session gesperrt.
#[tauri::command]
fn set_project_dir(project: String, dir: String) -> Result<(), String> {
if is_running(&project) {
return Err(format!("{project} läuft noch — erst beenden"));
}
set_project_dir_in(&Paths::real(), &project, &dir)
}
#[tauri::command]
fn add_work_dir(project: String, dir: String) -> Result<(), String> {
add_work_dir_in(&Paths::real(), &project, &dir)
@@ -1906,6 +1962,7 @@ fn main_builder() -> tauri::Builder<tauri::Wry> {
remove_project,
delete_project,
project_work_dirs,
set_project_dir,
add_work_dir,
remove_work_dir,
list_pools,
@@ -2753,6 +2810,24 @@ mod tests {
assert!(p.home.join("projects/extra").is_dir());
}
#[test]
fn projektordner_verlegen() {
let p = tmp_paths();
create_project_full_in(&p, "proj", None, None, None, false, TerminalConfig::default(), false)
.unwrap();
// Nutzer hat den Ordner selbst verschoben; die App ordnet nur neu zu.
let new_dir = p.home.join("elsewhere").join("proj");
fs::create_dir_all(p.home.join("elsewhere")).unwrap();
fs::rename(p.projects_dir().join("proj"), &new_dir).unwrap();
set_project_dir_in(&p, "proj", &new_dir.to_string_lossy()).unwrap();
assert_eq!(project_dir(&p, "proj").unwrap(), new_dir);
let raw = fs::read_to_string(settings_path(&new_dir)).unwrap();
assert!(raw.contains("~/elsewhere/proj/memory"));
assert!(raw.contains("Edit(~/elsewhere/proj/**)"));
assert!(!raw.contains("claude-projects/proj"));
}
#[test]
fn projekt_loeschen_unbekannt_scheitert() {
let p = tmp_paths();