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
JavaScript
;
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