UNPKG

markmv

Version:

TypeScript CLI for markdown file operations with intelligent link refactoring

190 lines • 7.41 kB
import { promises as fs } from 'node:fs'; import { dirname } from 'node:path'; import { AppendMergeStrategy, InteractiveMergeStrategy, PrependMergeStrategy, } from '../strategies/merge-strategies.js'; /** * Execute the merge command to combine content from one markdown file into another. * * This command provides intelligent merging of markdown content using various strategies and * conflict resolution. It supports: * * - Append strategy (add content to the end) * - Prepend strategy (add content to the beginning) * - Interactive strategy (resolve conflicts interactively) * - Obsidian transclusion creation for reference-based merging * - Frontmatter merging and conflict resolution * - Dry run mode for previewing changes * * The merge operation intelligently handles frontmatter, content sections, and provides * comprehensive conflict detection and resolution. * * @category Commands * * @example * Append merge with transclusions * ```typescript * await mergeCommand('notes/section.md', 'docs/handbook.md', { * strategy: 'append', * createTransclusions: true, * verbose: true * }); * ``` * * @example * Interactive merge with dry run * ```typescript * await mergeCommand('draft.md', 'final.md', { * strategy: 'interactive', * dryRun: true, * verbose: true * }); * ``` * * @example * Prepend merge * ```typescript * await mergeCommand('intro.md', 'document.md', { * strategy: 'prepend' * }); * ``` * * @param source - Path to the source markdown file to merge from * @param target - Path to the target markdown file to merge into * @param options - Configuration options for the merge operation * * @throws Will exit the process with code 1 if the operation fails */ export async function mergeCommand(source, target, options) { const strategy = options.strategy || 'interactive'; if (options.verbose) { console.log(`šŸ”€ Merging ${source} into ${target} using ${strategy} strategy`); if (options.dryRun) { console.log('šŸ” Dry run mode - no changes will be made'); } if (options.createTransclusions) { console.log('šŸ”— Creating Obsidian transclusions where possible'); } } try { // Check if files exist await fs.access(source); await fs.access(target); // Read file contents const sourceContent = await fs.readFile(source, 'utf8'); const targetContent = await fs.readFile(target, 'utf8'); // Choose strategy let mergeStrategy; switch (strategy) { case 'append': mergeStrategy = new AppendMergeStrategy({ createTransclusions: options.createTransclusions || false, }); break; case 'prepend': mergeStrategy = new PrependMergeStrategy({ createTransclusions: options.createTransclusions || false, }); break; default: mergeStrategy = new InteractiveMergeStrategy({ createTransclusions: options.createTransclusions || false, }); break; } // Perform the merge const result = await mergeStrategy.merge(targetContent, sourceContent, target, source); if (!result.success) { console.error('āŒ Merge operation failed:'); for (const error of result.errors) { console.error(` ${error}`); } process.exit(1); } // Build final content let finalContent = ''; if (result.frontmatter) { finalContent += result.frontmatter; if (!result.frontmatter.endsWith('\n')) { finalContent += '\n'; } finalContent += '\n'; } finalContent += result.content; // Display results if (options.dryRun) { console.log('\\nšŸ“‹ Changes that would be made:'); console.log('\\nšŸ“ File that would be modified:'); console.log(` ~ ${target}`); if (options.verbose) { console.log('\\nšŸ“„ Preview of merged content:'); const previewLines = finalContent.split('\\n').slice(0, 10); for (const line of previewLines) { console.log(` ${line}`); } if (finalContent.split('\\n').length > 10) { console.log(' ... (content truncated)'); } } console.log('\\nšŸ“Š Summary: Would modify 1 file'); } else { // Write the merged content await fs.mkdir(dirname(target), { recursive: true }); await fs.writeFile(target, finalContent, 'utf8'); console.log('āœ… Merge operation completed successfully!'); console.log(`šŸ“ Modified: ${target}`); if (result.transclusions.length > 0) { console.log(`\\nšŸ”— Created ${result.transclusions.length} transclusion(s):`); for (const transclusion of result.transclusions) { console.log(` + ${transclusion}`); } } } // Display conflicts if (result.conflicts.length > 0) { console.log('\\nāš ļø Conflicts detected:'); for (const conflict of result.conflicts) { console.log(` • ${conflict.type}: ${conflict.description}`); if (conflict.resolution) { console.log(` Resolution: ${conflict.resolution}`); } if (!conflict.autoResolved) { console.log(' āš ļø Manual resolution required'); } } } // Display warnings if (result.warnings.length > 0) { console.log('\\nāš ļø Warnings:'); for (const warning of result.warnings) { console.log(` ${warning}`); } } // Show helpful tips if (!options.dryRun && result.success) { console.log('\\nšŸ’” Tips:'); console.log(' • Use --dry-run to preview changes before merging'); console.log(' • Use --verbose for detailed operation logs'); if (strategy === 'append') { console.log(' • Content was appended to the end of the target file'); } else if (strategy === 'prepend') { console.log(' • Content was prepended to the beginning of the target file'); } else if (strategy === 'interactive') { console.log(' • Review merge conflicts and resolve manually if needed'); } console.log(' • Use --create-transclusions to create Obsidian-style references instead of copying content'); } } catch (error) { if (error && typeof error === 'object' && 'code' in error && error.code === 'ENOENT') { const pathValue = 'path' in error ? String(error.path) : 'unknown'; console.error(`āŒ File not found: ${pathValue}`); } else { console.error(`āŒ Unexpected error: ${error}`); } process.exit(1); } } //# sourceMappingURL=merge.js.map