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.
284 lines • 11.9 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;
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.FileOperationService = void 0;
const fs = __importStar(require("fs"));
const path = __importStar(require("path"));
const ImportUpdateService_1 = require("./ImportUpdateService");
const FileDiscoveryService_1 = require("./FileDiscoveryService");
/**
* Application service for handling file operations with automatic import updates
*/
class FileOperationService {
constructor() {
this.importUpdateService = new ImportUpdateService_1.ImportUpdateService();
this.fileDiscoveryService = new FileDiscoveryService_1.FileDiscoveryService();
}
/**
* Move a file to a new location and update all imports
* @param sourcePath The source file path
* @param targetPath The target file path
* @param createDirectory Whether to create the target directory if it doesn't exist
* @returns Promise<FileOperationResult>
*/
async moveFile(sourcePath, targetPath, createDirectory = true) {
try {
// Check if source file exists
if (!fs.existsSync(sourcePath)) {
return {
success: false,
message: `Source file does not exist: ${path.relative(process.cwd(), sourcePath)}`
};
}
// Check if target file already exists
if (fs.existsSync(targetPath)) {
return {
success: false,
message: `Target file already exists: ${path.relative(process.cwd(), targetPath)}`
};
}
// Create target directory if needed
if (createDirectory) {
const targetDirectory = path.dirname(targetPath);
if (!fs.existsSync(targetDirectory)) {
fs.mkdirSync(targetDirectory, { recursive: true });
}
}
// Move the file
fs.renameSync(sourcePath, targetPath);
// Update imports after the move
const importResult = await this.updateImportsAfterMove(sourcePath, targetPath);
// Clear file discovery cache
this.clearFileDiscoveryCache();
return {
success: true,
message: `Moved: ${path.relative(process.cwd(), sourcePath)} → ${path.relative(process.cwd(), targetPath)}`,
oldPath: sourcePath,
newPath: targetPath,
movedFile: {
oldPath: sourcePath,
newPath: targetPath,
oldRelativePath: path.relative(process.cwd(), sourcePath),
newRelativePath: path.relative(process.cwd(), targetPath)
}
};
}
catch (error) {
return {
success: false,
message: `Failed to move file: ${error instanceof Error ? error.message : String(error)}`
};
}
}
/**
* Clear file discovery cache to ensure fresh file discovery after operations
*/
clearFileDiscoveryCache() {
try {
this.fileDiscoveryService.clearCache();
}
catch (error) {
console.error(`❌ Failed to clear file discovery cache: ${error instanceof Error ? error.message : String(error)}`);
}
}
/**
* Rename a file and update all imports
* @param filePath The current file path
* @param newFileName The new file name (with extension)
* @returns Promise<FileOperationResult>
*/
async renameFile(filePath, newFileName) {
try {
// Check if source file exists
if (!fs.existsSync(filePath)) {
return {
success: false,
message: `Source file does not exist: ${path.relative(process.cwd(), filePath)}`
};
}
const directory = path.dirname(filePath);
const newFilePath = path.join(directory, newFileName);
// Check if target file already exists
if (fs.existsSync(newFilePath)) {
return {
success: false,
message: `Target file already exists: ${path.relative(process.cwd(), newFilePath)}`
};
}
// Rename the file
fs.renameSync(filePath, newFilePath);
// Update imports after the rename
const importResult = await this.updateImportsAfterMove(filePath, newFilePath);
// Clear file discovery cache
this.clearFileDiscoveryCache();
return {
success: true,
message: `Renamed: ${path.basename(filePath)} → ${newFileName}`,
oldPath: filePath,
newPath: newFilePath,
movedFile: {
oldPath: filePath,
newPath: newFilePath,
oldRelativePath: path.relative(process.cwd(), filePath),
newRelativePath: path.relative(process.cwd(), newFilePath)
}
};
}
catch (error) {
return {
success: false,
message: `Failed to rename file: ${error instanceof Error ? error.message : String(error)}`
};
}
}
/**
* Delete a file and update all imports (remove references to the deleted file)
* @param filePath The file path to delete
* @returns Promise<FileOperationResult>
*/
async deleteFile(filePath) {
try {
// Check if file exists
if (!fs.existsSync(filePath)) {
return {
success: false,
message: `File does not exist: ${path.relative(process.cwd(), filePath)}`
};
}
// Delete the file
fs.unlinkSync(filePath);
// Update imports to remove references to the deleted file
const importResult = await this.removeImportsForDeletedFile(filePath);
// Clear file discovery cache
this.clearFileDiscoveryCache();
return {
success: true,
message: `Deleted: ${path.relative(process.cwd(), filePath)}`,
oldPath: filePath
};
}
catch (error) {
return {
success: false,
message: `Failed to delete file: ${error instanceof Error ? error.message : String(error)}`
};
}
}
/**
* Update imports after a file has been moved
* @param oldPath The old file path
* @param newPath The new file path
* @returns Promise<{ updated: string[]; errors: string[] }>
*/
async updateImportsAfterMove(oldPath, newPath) {
try {
const movedFile = {
oldPath,
newPath,
oldRelativePath: path.relative(process.cwd(), oldPath),
newRelativePath: path.relative(process.cwd(), newPath)
};
const result = await this.importUpdateService.updateImportsAfterMove([movedFile]);
if (result.updated.length > 0) {
console.log(`🔄 Updated imports in ${result.updated.length} file(s)`);
}
if (result.errors.length > 0) {
console.log(`⚠️ Import update errors: ${result.errors.length}`);
result.errors.forEach(error => console.log(` - ${error}`));
}
return result;
}
catch (error) {
console.error(`❌ Failed to update imports: ${error instanceof Error ? error.message : String(error)}`);
return { updated: [], errors: [`Failed to update imports: ${error instanceof Error ? error.message : String(error)}`] };
}
}
/**
* Remove imports for a deleted file
* @param deletedFilePath The path of the deleted file
* @returns Promise<{ updated: string[]; errors: string[] }>
*/
async removeImportsForDeletedFile(deletedFilePath) {
try {
console.log(`🔄 Checking for imports of deleted file: ${path.relative(process.cwd(), deletedFilePath)}`);
// TODO: Implement import removal for deleted files
// This would involve:
// 1. Finding all files that import the deleted file
// 2. Removing those import statements
// 3. Cleaning up any unused imports
return { updated: [], errors: [] };
}
catch (error) {
console.error(`❌ Failed to remove imports for deleted file: ${error instanceof Error ? error.message : String(error)}`);
return { updated: [], errors: [`Failed to remove imports: ${error instanceof Error ? error.message : String(error)}`] };
}
}
/**
* Batch move multiple files and update all imports
* @param moves Array of { sourcePath, targetPath } objects
* @returns Promise<FileOperationResult[]>
*/
async batchMoveFiles(moves) {
const results = [];
const movedFiles = [];
// First, perform all moves
for (const move of moves) {
const result = await this.moveFile(move.sourcePath, move.targetPath);
results.push(result);
if (result.success && result.movedFile) {
movedFiles.push(result.movedFile);
}
}
// Then, update all imports in a single batch operation
if (movedFiles.length > 0) {
try {
const importResult = await this.importUpdateService.updateImportsAfterMove(movedFiles);
if (importResult.updated.length > 0) {
console.log(`🔄 Updated imports in ${importResult.updated.length} file(s)`);
}
if (importResult.errors.length > 0) {
console.log(`⚠️ Import update errors: ${importResult.errors.length}`);
importResult.errors.forEach(error => console.log(` - ${error}`));
}
}
catch (error) {
console.error(`❌ Failed to update imports in batch operation: ${error instanceof Error ? error.message : String(error)}`);
}
}
return results;
}
}
exports.FileOperationService = FileOperationService;
//# sourceMappingURL=FileOperationService.js.map