UNPKG

structure-validation

Version:

A Node.js CLI tool for validating codebase folder and file structure using a clean declarative configuration. Part of the guardz ecosystem for comprehensive TypeScript development.

258 lines • 10.6 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.AutoFixService = void 0; const promises_1 = __importDefault(require("fs/promises")); const path_1 = __importDefault(require("path")); const ImportUpdateService_1 = require("./ImportUpdateService"); /** * Application service for automatically fixing file structure issues */ class AutoFixService { constructor(basePath = process.cwd()) { this.basePath = basePath; this.importUpdateService = new ImportUpdateService_1.ImportUpdateService(basePath); } /** * Auto-fix validation issues by moving files to correct locations and updating imports */ async autoFix(suggestions, dryRun = true) { const fixed = []; const errors = []; const movedFiles = []; if (suggestions.length === 0) { return { fixed: [], errors: [], importUpdates: [], importErrors: [], summary: 'No files to fix' }; } console.log(`${dryRun ? 'šŸ”' : 'šŸ”§'} ${dryRun ? 'Dry run' : 'Auto-fixing'} ${suggestions.length} file(s)...\n`); // First, move all files for (const suggestion of suggestions) { try { const result = await this.moveFile(suggestion, dryRun); if (result.success) { fixed.push(result.message); if (!dryRun && result.movedFile) { movedFiles.push(result.movedFile); } } else { errors.push(result.message); } } catch (error) { errors.push(`Failed to process ${suggestion.file.relativePath}: ${error instanceof Error ? error.message : String(error)}`); } } // Then, update imports if files were actually moved let importUpdates = []; let importErrors = []; if (!dryRun && movedFiles.length > 0) { console.log(`\nšŸ”„ Updating import statements for ${movedFiles.length} moved file(s)...`); const importResult = await this.importUpdateService.updateImportsAfterMove(movedFiles); importUpdates = importResult.updated; importErrors = importResult.errors; } const summary = this.generateSummary(fixed, errors, importUpdates, importErrors, dryRun); return { fixed, errors, importUpdates, importErrors, summary }; } /** * Move a file to its suggested location */ async moveFile(suggestion, dryRun) { const sourcePath = suggestion.file.path; const targetDir = path_1.default.join(this.basePath, suggestion.suggestedFolder); const targetPath = path_1.default.join(targetDir, suggestion.file.filename); // Check if target directory exists if (!dryRun) { try { await promises_1.default.mkdir(targetDir, { recursive: true }); } catch (error) { return { success: false, message: `Failed to create directory ${suggestion.suggestedFolder}: ${error instanceof Error ? error.message : String(error)}` }; } } // Check if target file already exists try { await promises_1.default.access(targetPath); return { success: false, message: `Target file already exists: ${path_1.default.relative(this.basePath, targetPath)}` }; } catch { // File doesn't exist, safe to move } if (dryRun) { return { success: true, message: `Would move: ${suggestion.file.relativePath} → ${path_1.default.relative(this.basePath, targetPath)}` }; } try { await promises_1.default.rename(sourcePath, targetPath); const movedFile = { oldPath: sourcePath, newPath: targetPath, oldRelativePath: suggestion.file.relativePath, newRelativePath: path_1.default.relative(this.basePath, targetPath) }; return { success: true, message: `Moved: ${suggestion.file.relativePath} → ${path_1.default.relative(this.basePath, targetPath)}`, movedFile }; } catch (error) { return { success: false, message: `Failed to move ${suggestion.file.relativePath}: ${error instanceof Error ? error.message : String(error)}` }; } } /** * Generate a comprehensive summary of the auto-fix operation */ generateSummary(fixed, errors, importUpdates, importErrors, dryRun) { const totalFiles = fixed.length; const totalErrors = errors.length; const totalImportUpdates = importUpdates.length; const totalImportErrors = importErrors.length; let summary = `\nšŸ”§ Auto-fix ${dryRun ? 'simulation' : 'completed'}:\n`; summary += ` • Files processed: ${totalFiles + totalErrors}\n`; summary += ` • Successfully moved: ${totalFiles}\n`; summary += ` • Move errors: ${totalErrors}\n`; if (!dryRun && (totalImportUpdates > 0 || totalImportErrors > 0)) { summary += ` • Import updates: ${totalImportUpdates}\n`; summary += ` • Import errors: ${totalImportErrors}\n`; } if (totalFiles > 0) { const successRate = Math.round((totalFiles / (totalFiles + totalErrors)) * 100); summary += ` • Success rate: ${successRate}%\n`; } if (dryRun) { summary += `\nšŸ’” This was a dry run. Use --fix to actually move files and update imports.`; } else if (totalImportUpdates > 0) { summary += `\nāœ… Files moved and imports updated successfully!`; } else if (totalFiles > 0) { summary += `\nāœ… Files moved successfully!`; } return summary; } /** * Create a backup of all source files before making changes */ async createBackup() { const timestamp = new Date().toISOString().replace(/[:.]/g, '-'); const backupDir = path_1.default.join(this.basePath, '.structure-validation-backup', timestamp); try { await promises_1.default.mkdir(backupDir, { recursive: true }); const sourceFiles = await this.getAllSourceFiles(); for (const filePath of sourceFiles) { const relativePath = path_1.default.relative(this.basePath, filePath); const backupPath = path_1.default.join(backupDir, relativePath); const backupDirPath = path_1.default.dirname(backupPath); await promises_1.default.mkdir(backupDirPath, { recursive: true }); await promises_1.default.copyFile(filePath, backupPath); } return backupDir; } catch (error) { throw new Error(`Failed to create backup: ${error instanceof Error ? error.message : String(error)}`); } } /** * Get all source files in the project */ async getAllSourceFiles() { const { glob } = await Promise.resolve().then(() => __importStar(require('fast-glob'))); const patterns = [ '**/*.ts', '**/*.tsx', '**/*.js', '**/*.jsx' ]; const excludePatterns = [ 'node_modules/**', 'dist/**', 'build/**', '.git/**', 'coverage/**', '.structure-validation-backup/**' ]; return await glob(patterns, { cwd: this.basePath, ignore: excludePatterns, absolute: true }); } /** * Restore files from a backup */ async restoreFromBackup(backupDir) { try { const { glob } = await Promise.resolve().then(() => __importStar(require('fast-glob'))); const backupFiles = await glob('**/*', { cwd: backupDir, absolute: true, onlyFiles: true }); for (const backupFile of backupFiles) { const relativePath = path_1.default.relative(backupDir, backupFile); const targetPath = path_1.default.join(this.basePath, relativePath); const targetDir = path_1.default.dirname(targetPath); await promises_1.default.mkdir(targetDir, { recursive: true }); await promises_1.default.copyFile(backupFile, targetPath); } } catch (error) { throw new Error(`Failed to restore from backup: ${error instanceof Error ? error.message : String(error)}`); } } } exports.AutoFixService = AutoFixService; //# sourceMappingURL=AutoFixService.js.map