Files
MyNote/apps/desktop/src/main/services/migration-service.ts
T

69 lines
2.9 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// MigrationService — 旧版数据迁移(FR-D1/D3)
import nodePath from 'node:path';
import { promises as fs } from 'node:fs';
import { MD_EXT } from '@hunter/shared';
import { joinFrontmatter } from '../safe/frontmatter-codec.js';
export interface MigrationResult {
migrated: boolean;
count: number;
errors: { path: string; reason: string }[];
}
export class MigrationService {
// FR-D1:检测旧版板块三级结构数据(.hunter/boards/),转换为文件库结构
async detectAndMigrate(root: string): Promise<MigrationResult> {
const oldDataDir = nodePath.join(root, '.hunter', 'boards');
const exists = await fs.access(oldDataDir).then(() => true).catch(() => false);
if (!exists) return { migrated: false, count: 0, errors: [] };
const errors: { path: string; reason: string }[] = [];
let count = 0;
try {
const boards = await fs.readdir(oldDataDir, { withFileTypes: true });
for (const board of boards) {
if (!board.isDirectory()) continue;
const boardDir = nodePath.join(oldDataDir, board.name);
try {
const targetDir = nodePath.join(root, board.name);
await fs.mkdir(targetDir, { recursive: true });
await this.convertDir(boardDir, targetDir);
count++;
} catch (e) {
errors.push({ path: board.name, reason: e instanceof Error ? e.message : String(e) });
}
}
if (errors.length === 0) {
await fs.rename(oldDataDir, nodePath.join(root, '.hunter', 'boards.migrated'));
}
} catch (e) {
errors.push({ path: oldDataDir, reason: e instanceof Error ? e.message : String(e) });
}
return { migrated: count > 0, count, errors };
}
private async convertDir(srcAbs: string, destAbs: string): Promise<void> {
const entries = await fs.readdir(srcAbs, { withFileTypes: true });
for (const e of entries) {
const srcPath = nodePath.join(srcAbs, e.name);
if (e.isDirectory()) {
const destPath = nodePath.join(destAbs, e.name);
await fs.mkdir(destPath, { recursive: true });
await this.convertDir(srcPath, destPath);
} else if (e.isFile() && e.name.endsWith('.json')) {
const raw = await fs.readFile(srcPath, 'utf8');
const data = JSON.parse(raw);
const title = data.title ?? e.name.slice(0, -5);
const body = data.content ?? '';
const meta: Record<string, unknown> = { title, createdAt: data.createdAt ?? Date.now() };
if (data.tags) meta.tags = data.tags;
if (data.star) meta.star = true;
const mdName = String(title).replace(/[^\w\u4e00-\u9fa5\-]/g, '_') + MD_EXT;
await fs.writeFile(nodePath.join(destAbs, mdName), joinFrontmatter(meta as any, body), 'utf8');
} else if (e.isFile() && e.name.endsWith(MD_EXT)) {
await fs.copyFile(srcPath, nodePath.join(destAbs, e.name));
}
}
}
}