UNPKG

legal-markdown-js

Version:

Node.js implementation of LegalMarkdown for processing legal documents with markdown and YAML - Complete feature parity with Ruby version

244 lines 9.78 kB
/** * Archive Manager Utility * * This module handles moving processed source files to archive directories * with conflict resolution, error handling, and directory management. * It provides a clean API for organizing files after successful processing. */ import * as fs from 'fs'; import * as path from 'path'; import { generateArchivePaths, areContentsIdentical } from './file-naming.js'; /** * Archive Manager class for handling file archiving operations */ export class ArchiveManager { /** * Archive a file to the specified directory * * @param sourcePath Path to the source file to archive * @param options Archive configuration options * @returns Promise resolving to the archive result */ async archiveFile(sourcePath, options) { try { // Validate source file exists if (!fs.existsSync(sourcePath)) { return { success: false, originalPath: sourcePath, error: 'Source file does not exist', }; } // Ensure source is a file, not a directory const sourceStats = fs.statSync(sourcePath); if (!sourceStats.isFile()) { return { success: false, originalPath: sourcePath, error: 'Source path is not a file', }; } // Resolve absolute paths const absoluteSourcePath = path.resolve(sourcePath); const absoluteArchiveDir = path.resolve(options.archiveDir); // Create archive directory if needed if (options.createDirectory !== false) { this.ensureDirectoryExists(absoluteArchiveDir); } // Determine target file path const fileName = path.basename(absoluteSourcePath); let targetPath = path.join(absoluteArchiveDir, fileName); // Handle conflicts if (fs.existsSync(targetPath)) { const resolvedPath = this.handleConflicts(targetPath, options.conflictResolution || 'rename'); if (!resolvedPath) { return { success: false, originalPath: sourcePath, error: 'File already exists and conflict resolution is set to skip', }; } targetPath = resolvedPath; } // Move the file fs.renameSync(absoluteSourcePath, targetPath); return { success: true, originalPath: sourcePath, archivedPath: targetPath, }; } catch (error) { return { success: false, originalPath: sourcePath, error: error instanceof Error ? error.message : 'Unknown error occurred', }; } } /** * Generate a unique filename by adding a numeric suffix * * @param targetPath The original target path * @returns A unique file path that doesn't conflict with existing files */ generateUniqueFilename(targetPath) { const dir = path.dirname(targetPath); const ext = path.extname(targetPath); const baseName = path.basename(targetPath, ext); let counter = 1; let uniquePath; do { uniquePath = path.join(dir, `${baseName}_${counter}${ext}`); counter++; } while (fs.existsSync(uniquePath)); return uniquePath; } /** * Ensure a directory exists, creating it recursively if necessary * * @param dirPath Path to the directory to create * @throws Error if directory creation fails */ ensureDirectoryExists(dirPath) { try { if (!fs.existsSync(dirPath)) { fs.mkdirSync(dirPath, { recursive: true }); } } catch (error) { const errorMessage = error instanceof Error ? error.message : 'Unknown error'; throw new Error(`Failed to create archive directory: ${dirPath}. ${errorMessage}`); } } /** * Smart archive that compares original and processed content * * If contents are identical, archives only the original file. * If contents are different, archives both with .ORIGINAL and .PROCESSED suffixes. * * @param sourcePath Path to the source file * @param options Smart archive configuration options * @returns Promise resolving to the smart archive result */ async smartArchiveFile(sourcePath, options) { try { // Compare contents const contentsIdentical = areContentsIdentical(options.originalContent, options.processedContent); // Resolve absolute paths const absoluteSourcePath = path.resolve(sourcePath); const absoluteArchiveDir = path.resolve(options.archiveDir); const fileName = path.basename(absoluteSourcePath); // Create archive directory if needed if (options.createDirectory !== false) { this.ensureDirectoryExists(absoluteArchiveDir); } if (contentsIdentical) { // Contents are identical, just archive the original file const targetPath = path.join(absoluteArchiveDir, fileName); const resolvedPath = this.handleConflicts(targetPath, options.conflictResolution); if (!resolvedPath) { return { success: false, originalPath: sourcePath, contentsIdentical: true, error: 'File already exists and conflict resolution is set to skip', }; } // Move the original file fs.renameSync(absoluteSourcePath, resolvedPath); return { success: true, originalPath: sourcePath, archivedPath: resolvedPath, contentsIdentical: true, }; } else { // Contents are different, archive both with suffixes const basePath = path.join(absoluteArchiveDir, fileName); const archivePaths = generateArchivePaths(basePath); // Handle conflicts for both files const resolvedOriginalPath = this.handleConflicts(archivePaths.original, options.conflictResolution); const resolvedProcessedPath = this.handleConflicts(archivePaths.processed, options.conflictResolution); if (!resolvedOriginalPath || !resolvedProcessedPath) { return { success: false, originalPath: sourcePath, contentsIdentical: false, error: 'One or more archive files already exist and conflict resolution is set to skip', }; } // Write both files fs.writeFileSync(resolvedOriginalPath, options.originalContent, 'utf8'); fs.writeFileSync(resolvedProcessedPath, options.processedContent, 'utf8'); // Remove the original source file fs.unlinkSync(absoluteSourcePath); return { success: true, originalPath: sourcePath, contentsIdentical: false, archivedOriginalPath: resolvedOriginalPath, archivedProcessedPath: resolvedProcessedPath, archivedPath: resolvedProcessedPath, // Primary path for backward compatibility }; } } catch (error) { return { success: false, originalPath: sourcePath, contentsIdentical: false, error: error instanceof Error ? error.message : 'Unknown error occurred', }; } } /** * Handle filename conflicts using the specified strategy * * @param targetPath The proposed target file path * @param strategy The conflict resolution strategy * @returns The resolved file path, or null if skipping */ handleConflicts(targetPath, strategy = 'rename') { if (!fs.existsSync(targetPath)) { return targetPath; } switch (strategy) { case 'overwrite': return targetPath; case 'skip': return null; case 'rename': default: return this.generateUniqueFilename(targetPath); } } /** * Check if a path can be used as an archive directory * * @param dirPath Path to check * @returns True if the path is valid for archiving */ static isValidArchiveDirectory(dirPath) { try { const resolvedPath = path.resolve(dirPath); // Check if it exists and is a directory if (fs.existsSync(resolvedPath)) { const stats = fs.statSync(resolvedPath); return stats.isDirectory(); } // If it doesn't exist, check if we can create it const parentDir = path.dirname(resolvedPath); if (fs.existsSync(parentDir)) { const parentStats = fs.statSync(parentDir); return parentStats.isDirectory(); } return false; } catch { return false; } } } //# sourceMappingURL=archive-manager.js.map