fix(project): prevent deleted projects from resurrecting
- Migrate legacy project zips by moving instead of copying - Remove legacy zip copies on project delete - Add contract test for legacy migration/delete behavior Made-with: Cursor
This commit is contained in:
@@ -100,7 +100,21 @@ export class ZipProjectStore {
|
||||
try {
|
||||
const st = await fs.stat(from);
|
||||
if (!st.isFile()) continue;
|
||||
await fs.copyFile(from, to);
|
||||
// Переносим (а не копируем), чтобы:
|
||||
// - не было дублей между разными appName
|
||||
// - удалённые пользователем проекты не «возрождались» при следующем ensureRoots()
|
||||
try {
|
||||
await fs.rename(from, to);
|
||||
} catch {
|
||||
await fs.copyFile(from, to);
|
||||
try {
|
||||
await rmWithRetries(fs.rm, from, { force: true });
|
||||
} catch {
|
||||
// best effort: если zip уже скопирован в dest, миграцию считаем успешной;
|
||||
// legacy-копия может остаться (например из-за lock/AV), но удаление проекта
|
||||
// затем чистит legacy по fileName.
|
||||
}
|
||||
}
|
||||
destZips.add(name);
|
||||
} catch {
|
||||
/* ignore */
|
||||
@@ -795,6 +809,18 @@ export class ZipProjectStore {
|
||||
|
||||
await rmWithRetries(fs.rm, zipPath, { force: true });
|
||||
await rmWithRetries(fs.rm, cacheDir, { recursive: true, force: true });
|
||||
|
||||
// Если проект подтянулся миграцией из legacy userData (другое имя приложения),
|
||||
// то после удаления из текущей папки он может снова появиться при следующем ensureRoots().
|
||||
// Поэтому удаляем и legacy-копии архива.
|
||||
for (const legacyRoot of getLegacyProjectsRootDirs()) {
|
||||
const legacyZipPath = path.join(legacyRoot, entry.fileName);
|
||||
try {
|
||||
await rmWithRetries(fs.rm, legacyZipPath, { force: true });
|
||||
} catch {
|
||||
/* ignore */
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private randomId(): string {
|
||||
|
||||
Reference in New Issue
Block a user