M0-M11 狩夜人笔记初始实现:11里程碑+86测试

This commit is contained in:
hunter-dev
2026-08-24 20:35:22 +08:00
commit 3ebd33536b
106 changed files with 10490 additions and 0 deletions
@@ -0,0 +1,47 @@
// MigrationService — 旧版数据迁移判定与幂等保护(FR-D1/D2/D3)
import { promises as fs } from 'node:fs';
import nodePath from 'node:path';
export interface MigrationResult { migrated: boolean; count: number; reason?: string; }
export class MigrationService {
constructor(private userDataDir: string) {}
private flagFile() { return nodePath.join(this.userDataDir, 'migrated.flag'); }
async isMigrated(): Promise<boolean> {
try { await fs.access(this.flagFile()); return true; } catch { return false; }
}
// 判定是否应迁移:旧数据存在 + 未迁移过 + 新库为空(三者同时满足,FR-D2)
async shouldMigrate(oldDataDir: string, newVaultRoot: string): Promise<boolean> {
if (await this.isMigrated()) return false;
const oldExists = await fs.stat(oldDataDir).then(s => s.isDirectory()).catch(() => false);
if (!oldExists) return false;
// 新库为空(无 .md 文件)
const entries = await fs.readdir(newVaultRoot).catch(() => []);
const hasMd = entries.some(e => e.endsWith('.md'));
return !hasMd;
}
// 执行迁移(骨架:实际板块→文件夹映射在集成时落地)
async migrate(oldDataDir: string, newVaultRoot: string): Promise<MigrationResult> {
if (!(await this.shouldMigrate(oldDataDir, newVaultRoot))) {
return { migrated: false, count: 0, reason: '条件不满足(已迁移/无旧数据/新库非空)' };
}
try {
// TODO: 旧板块三级结构 → 文件夹嵌套;旧富文本 → Markdown;附件 → .attachments;标签与关联映射
// 骨架阶段:仅写迁移标志,count=0
await this.markMigrated();
return { migrated: true, count: 0 };
} catch (e) {
// 失败不记标志(下次可重试,FR-D3)
return { migrated: false, count: 0, reason: e instanceof Error ? e.message : String(e) };
}
}
async markMigrated() {
await fs.mkdir(nodePath.dirname(this.flagFile()), { recursive: true });
await fs.writeFile(this.flagFile(), new Date().toISOString(), 'utf8');
}
}