158 lines
4.3 KiB
JavaScript
158 lines
4.3 KiB
JavaScript
#!/usr/bin/env node
|
|
|
|
/**
|
|
* Migration script for converting old flat JSON Excalidraw files to Obsidian format
|
|
* Usage: node server/migrate-excalidraw.mjs [--dry-run] [--vault-path=./vault]
|
|
*/
|
|
|
|
import fs from 'fs';
|
|
import path from 'path';
|
|
import { fileURLToPath } from 'url';
|
|
import { parseFlatJson, toObsidianExcalidrawMd, isValidExcalidrawScene } from './excalidraw-obsidian.mjs';
|
|
|
|
const __filename = fileURLToPath(import.meta.url);
|
|
const __dirname = path.dirname(__filename);
|
|
|
|
// Parse command line arguments
|
|
const args = process.argv.slice(2);
|
|
const dryRun = args.includes('--dry-run');
|
|
const vaultPathArg = args.find(arg => arg.startsWith('--vault-path='));
|
|
const vaultPath = vaultPathArg
|
|
? path.resolve(vaultPathArg.split('=')[1])
|
|
: path.resolve(__dirname, '..', 'vault');
|
|
|
|
console.log('🔄 Excalidraw Migration Tool');
|
|
console.log('━'.repeat(50));
|
|
console.log(`Vault path: ${vaultPath}`);
|
|
console.log(`Mode: ${dryRun ? 'DRY RUN (no changes)' : 'LIVE (will modify files)'}`);
|
|
console.log('━'.repeat(50));
|
|
console.log();
|
|
|
|
if (!fs.existsSync(vaultPath)) {
|
|
console.error(`❌ Vault directory not found: ${vaultPath}`);
|
|
process.exit(1);
|
|
}
|
|
|
|
let filesScanned = 0;
|
|
let filesConverted = 0;
|
|
let filesSkipped = 0;
|
|
let filesErrored = 0;
|
|
|
|
/**
|
|
* Recursively scan directory for Excalidraw files
|
|
*/
|
|
function scanDirectory(dir) {
|
|
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
|
|
for (const entry of entries) {
|
|
const fullPath = path.join(dir, entry.name);
|
|
|
|
if (entry.isDirectory()) {
|
|
// Skip hidden directories
|
|
if (entry.name.startsWith('.')) continue;
|
|
scanDirectory(fullPath);
|
|
continue;
|
|
}
|
|
|
|
if (!entry.isFile()) continue;
|
|
|
|
const lower = entry.name.toLowerCase();
|
|
|
|
// Look for .excalidraw or .json files (but not .excalidraw.md)
|
|
if (lower.endsWith('.excalidraw.md')) {
|
|
// Already in Obsidian format, skip
|
|
filesSkipped++;
|
|
continue;
|
|
}
|
|
|
|
if (!lower.endsWith('.excalidraw') && !lower.endsWith('.json')) {
|
|
continue;
|
|
}
|
|
|
|
filesScanned++;
|
|
processFile(fullPath);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Process a single file
|
|
*/
|
|
function processFile(filePath) {
|
|
const relativePath = path.relative(vaultPath, filePath);
|
|
|
|
try {
|
|
const content = fs.readFileSync(filePath, 'utf-8');
|
|
|
|
// Try to parse as flat JSON
|
|
const scene = parseFlatJson(content);
|
|
|
|
if (!scene || !isValidExcalidrawScene(scene)) {
|
|
console.log(`⏭️ Skipped (not valid Excalidraw): ${relativePath}`);
|
|
filesSkipped++;
|
|
return;
|
|
}
|
|
|
|
// Convert to Obsidian format
|
|
const obsidianMd = toObsidianExcalidrawMd(scene);
|
|
|
|
// Determine new file path
|
|
const dir = path.dirname(filePath);
|
|
const baseName = path.basename(filePath, path.extname(filePath));
|
|
const newPath = path.join(dir, `${baseName}.excalidraw.md`);
|
|
|
|
if (dryRun) {
|
|
console.log(`✅ Would convert: ${relativePath} → ${path.basename(newPath)}`);
|
|
filesConverted++;
|
|
return;
|
|
}
|
|
|
|
// Create backup of original
|
|
const backupPath = filePath + '.bak';
|
|
fs.copyFileSync(filePath, backupPath);
|
|
|
|
// Write new file
|
|
fs.writeFileSync(newPath, obsidianMd, 'utf-8');
|
|
|
|
// Remove original if new file is different
|
|
if (newPath !== filePath) {
|
|
fs.unlinkSync(filePath);
|
|
}
|
|
|
|
console.log(`✅ Converted: ${relativePath} → ${path.basename(newPath)}`);
|
|
filesConverted++;
|
|
|
|
} catch (error) {
|
|
console.error(`❌ Error processing ${relativePath}:`, error.message);
|
|
filesErrored++;
|
|
}
|
|
}
|
|
|
|
// Run migration
|
|
try {
|
|
scanDirectory(vaultPath);
|
|
|
|
console.log();
|
|
console.log('━'.repeat(50));
|
|
console.log('📊 Migration Summary');
|
|
console.log('━'.repeat(50));
|
|
console.log(`Files scanned: ${filesScanned}`);
|
|
console.log(`Files converted: ${filesConverted}`);
|
|
console.log(`Files skipped: ${filesSkipped}`);
|
|
console.log(`Files errored: ${filesErrored}`);
|
|
console.log('━'.repeat(50));
|
|
|
|
if (dryRun) {
|
|
console.log();
|
|
console.log('💡 This was a dry run. Run without --dry-run to apply changes.');
|
|
} else if (filesConverted > 0) {
|
|
console.log();
|
|
console.log('✅ Migration complete! Backup files (.bak) were created.');
|
|
}
|
|
|
|
process.exit(filesErrored > 0 ? 1 : 0);
|
|
|
|
} catch (error) {
|
|
console.error('❌ Migration failed:', error);
|
|
process.exit(1);
|
|
}
|