Add import/export and backup system (Milestone 8)
- Obsidian vault importer with wikilink → edge conversion - Markdown folder importer with frontmatter parsing - Markdown exporter with wikilinks and frontmatter - JSON-LD linked data exporter - Database backup/restore functionality - CLI: import, backup, restore-backup, list-backups - MCP tools: memory_import, memory_backup, memory_export_markdown, memory_export_jsonld
This commit is contained in:
93
src/core/import/markdown.ts
Normal file
93
src/core/import/markdown.ts
Normal file
@@ -0,0 +1,93 @@
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import { addNode } from '../store';
|
||||
import { NodeKind } from '../../types';
|
||||
|
||||
export interface MarkdownImportOptions {
|
||||
kind?: NodeKind;
|
||||
tags?: string[];
|
||||
dryRun?: boolean;
|
||||
}
|
||||
|
||||
export interface MarkdownImportResult {
|
||||
imported: number;
|
||||
files: string[];
|
||||
}
|
||||
|
||||
export async function importMarkdown(folderPath: string, options: MarkdownImportOptions = {}): Promise<MarkdownImportResult> {
|
||||
const absPath = path.resolve(folderPath);
|
||||
|
||||
if (!fs.existsSync(absPath)) {
|
||||
throw new Error(`Folder does not exist: ${absPath}`);
|
||||
}
|
||||
|
||||
const files = findMarkdownFiles(absPath);
|
||||
const defaultKind = options.kind || 'memory';
|
||||
const defaultTags = options.tags || [];
|
||||
|
||||
if (options.dryRun) {
|
||||
return {
|
||||
imported: files.length,
|
||||
files: files.map(f => path.relative(absPath, f)),
|
||||
};
|
||||
}
|
||||
|
||||
let imported = 0;
|
||||
const importedFiles: string[] = [];
|
||||
|
||||
for (const file of files) {
|
||||
const content = fs.readFileSync(file, 'utf-8');
|
||||
const relativePath = path.relative(absPath, file);
|
||||
const title = path.basename(file, '.md');
|
||||
|
||||
// Extract title from first H1 if present
|
||||
const h1Match = content.match(/^#\s+(.+)$/m);
|
||||
const nodeTitle = h1Match ? h1Match[1] : title;
|
||||
|
||||
// Remove the H1 from content if it was used as title
|
||||
const nodeContent = h1Match ? content.replace(/^#\s+.+\n*/, '') : content;
|
||||
|
||||
await addNode({
|
||||
kind: defaultKind,
|
||||
title: nodeTitle,
|
||||
content: nodeContent.trim(),
|
||||
tags: ['imported', 'markdown', ...defaultTags],
|
||||
metadata: {
|
||||
importedFrom: 'markdown',
|
||||
originalPath: relativePath,
|
||||
importedAt: Date.now(),
|
||||
},
|
||||
});
|
||||
|
||||
imported++;
|
||||
importedFiles.push(relativePath);
|
||||
}
|
||||
|
||||
return {
|
||||
imported,
|
||||
files: importedFiles,
|
||||
};
|
||||
}
|
||||
|
||||
function findMarkdownFiles(dir: string): string[] {
|
||||
const files: string[] = [];
|
||||
|
||||
function walk(currentDir: string) {
|
||||
const entries = fs.readdirSync(currentDir, { withFileTypes: true });
|
||||
|
||||
for (const entry of entries) {
|
||||
const fullPath = path.join(currentDir, entry.name);
|
||||
|
||||
if (entry.name.startsWith('.')) continue;
|
||||
|
||||
if (entry.isDirectory()) {
|
||||
walk(fullPath);
|
||||
} else if (entry.isFile() && entry.name.endsWith('.md')) {
|
||||
files.push(fullPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
walk(dir);
|
||||
return files;
|
||||
}
|
||||
Reference in New Issue
Block a user