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.

401 lines • 21.2 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; }; })(); Object.defineProperty(exports, "__esModule", { value: true }); exports.AddFilePatternToConfigService = void 0; const fs = __importStar(require("fs")); const path = __importStar(require("path")); /** * Dedicated service for adding file patterns to structure-validation.config.json * This service is completely independent with no code reuse from other services */ class AddFilePatternToConfigService { constructor(rl) { this.rl = rl; } /** * Execute the add file pattern to config action * @param filePath The path of the file * @param fileName The name of the file * @returns Promise<void> */ async executeAddFilePatternToConfig(filePath, fileName) { console.log(`šŸ” executeAddFilePatternToConfig called with:`); console.log(` - filePath: "${filePath}"`); console.log(` - fileName: "${fileName}"`); const configPath = path.join(process.cwd(), 'structure-validation.config.json'); console.log(` - configPath: "${configPath}"`); if (!fs.existsSync(configPath)) { console.log('āŒ structure-validation.config.json not found in project root.'); return; } try { // Generate a smart pattern based on the file name analysis const suggestedPattern = this.generateSuggestedPattern(fileName); // Read the current config const configContent = fs.readFileSync(configPath, 'utf8'); // Parse the JSON config to get the actual structure const config = JSON.parse(configContent); const existingFolders = Object.keys(config.structure || config); // Extract folder name from file path const fileDir = path.dirname(filePath); const projectRoot = process.cwd(); const relativePath = path.relative(projectRoot, fileDir); const folderName = relativePath.split(path.sep).pop() || ''; console.log(` - Extracted folder name: "${folderName}"`); // Get the root path from config const configRoot = config.root || '.'; const absoluteConfigRoot = path.resolve(projectRoot, configRoot); // Check if the file should be added to {root} folder in config // This happens when the file is in the project root (as defined in config) const isFileInProjectRoot = fileDir === absoluteConfigRoot; console.log(` - File is in project root: ${isFileInProjectRoot}`); console.log(` - File directory: ${fileDir}`); console.log(` - Config root: ${absoluteConfigRoot}`); // Check if the folder exists in config const folderExists = existingFolders.includes(folderName); console.log(` - Folder "${folderName}" exists in config: ${folderExists}`); // Create the list of available folders let availableFolders = [...existingFolders]; let newFolderAdded = false; // Only add the folder as a new option if the file is in the project root // and the folder doesn't exist in config if (!folderExists && folderName && isFileInProjectRoot) { // Add the folder to the list if it doesn't exist and file is in project root availableFolders.push(folderName); newFolderAdded = true; console.log(` - Added "${folderName}" to available folders list`); } return new Promise((resolve) => { this.rl.question(`\nSuggested pattern: "${suggestedPattern}"\nEnter the pattern you want to add (or press Enter to use the suggested pattern): `, async (patternAnswer) => { const finalPattern = patternAnswer.trim() || suggestedPattern; if (!finalPattern) { console.log('āŒ Pattern cannot be empty.'); resolve(); return; } console.log(`\nUsing pattern: "${finalPattern}"`); // Show available folders after pattern selection console.log('\nAvailable folders in config:'); availableFolders.forEach((folder, index) => { const marker = folder === folderName && newFolderAdded ? ' (NEW)' : ''; console.log(` ${index + 1} - ${folder}${marker}`); }); console.log(` ${availableFolders.length + 1} - Create new folder`); // Determine default choice based on file location let defaultChoice = availableFolders.length + 1; // Default to "Create new folder" let defaultMessage = `(1-${availableFolders.length + 1}, default: ${defaultChoice})`; if (isFileInProjectRoot) { // If file is in project root, default to {root} folder (if it exists) or create new const rootIndex = availableFolders.findIndex(folder => folder === '{root}'); if (rootIndex !== -1) { defaultChoice = rootIndex + 1; defaultMessage = `(1-${availableFolders.length + 1}, default: ${defaultChoice})`; } else { // {root} folder doesn't exist, default to creating new defaultChoice = availableFolders.length + 1; defaultMessage = `(1-${availableFolders.length + 1}, default: ${defaultChoice})`; } } else { // If file is not in project root (i.e., in a subfolder), default to {root} folder const rootIndex = availableFolders.findIndex(folder => folder === '{root}'); if (rootIndex !== -1) { defaultChoice = rootIndex + 1; defaultMessage = `(1-${availableFolders.length + 1}, default: ${defaultChoice})`; } else { // {root} folder doesn't exist, default to creating new defaultChoice = availableFolders.length + 1; defaultMessage = `(1-${availableFolders.length + 1}, default: ${defaultChoice})`; } } this.rl.question(`Which folder would you like to add the pattern "${finalPattern}" to? ${defaultMessage}: `, async (answer) => { let choice = parseInt(answer.trim()); // If no input provided, use default choice if (isNaN(choice) || choice < 1 || choice > availableFolders.length + 1) { if (answer.trim() === '') { choice = defaultChoice; console.log(`Using default choice: ${choice}`); } else { console.log(`āŒ Invalid choice "${answer}". Please enter a number between 1 and ${availableFolders.length + 1}.`); resolve(); return; } } if (choice <= availableFolders.length) { // Add to existing folder or newly added folder const targetFolder = availableFolders[choice - 1]; if (!targetFolder) { console.log('āŒ Invalid folder selection.'); resolve(); return; } // Check if this is a newly added folder that needs to be created if (newFolderAdded && targetFolder === folderName) { // Create the new folder with the pattern await this.createNewFolderWithPattern(configContent, configPath, existingFolders, finalPattern, resolve, targetFolder); } else { // Add to existing folder await this.addPatternToExistingFolder(configContent, configPath, targetFolder, finalPattern); resolve(); } } else { // Create new folder await this.createNewFolderWithPattern(configContent, configPath, existingFolders, finalPattern, resolve); } }); }); }); } catch (error) { console.error(`āŒ Failed to update config: ${error instanceof Error ? error.message : String(error)}`); } } /** * Generate a suggested pattern based on file name analysis * @param fileName The file name to analyze * @returns string The suggested pattern */ generateSuggestedPattern(fileName) { const fileExt = path.extname(fileName); // Generate a smart pattern based on the file name analysis let suggestedPattern = `*${fileExt}`; // Extract the base name without extension const baseName = path.basename(fileName, fileExt); // Check for patterns with dots (e.g., .guardz., .constant., etc.) const dotParts = baseName.split('.'); if (dotParts.length > 1) { // Get the last part before the extension (e.g., "guardz" from "isApiKey.guardz") const lastPart = dotParts[dotParts.length - 1]; suggestedPattern = `*.${lastPart}${fileExt}`; } else if (baseName.startsWith('use')) { // Hook pattern suggestedPattern = `use*${fileExt}`; } else if (baseName.startsWith('is')) { // Guard pattern suggestedPattern = `is*${fileExt}`; } else if (baseName.endsWith('Enum')) { // Enum pattern suggestedPattern = `*Enum${fileExt}`; } return suggestedPattern; } /** * Add pattern to existing folder * @param configContent The current config content * @param configPath The config file path * @param targetFolder The target folder name * @param suggestedPattern The pattern to add * @returns Promise<void> */ async addPatternToExistingFolder(configContent, configPath, targetFolder, suggestedPattern) { console.log(`šŸ” addPatternToExistingFolder called with:`); console.log(` - targetFolder: "${targetFolder}"`); console.log(` - suggestedPattern: "${suggestedPattern}"`); console.log(` - configPath: "${configPath}"`); if (!targetFolder) { console.log('āŒ Invalid folder selection.'); return; } try { // Parse the JSON content const config = JSON.parse(configContent); // Check if the target folder exists in the structure if (!config.structure || !config.structure[targetFolder]) { console.log(`āŒ Folder "${targetFolder}" not found in config structure.`); return; } // Check if the pattern already exists if (config.structure[targetFolder].includes(suggestedPattern)) { console.log(`āš ļø Pattern "${suggestedPattern}" already exists in "${targetFolder}" folder.`); return; } // Add the pattern to the existing folder config.structure[targetFolder].push(suggestedPattern); console.log(`šŸ“ Added pattern "${suggestedPattern}" to existing "${targetFolder}" folder`); // Write the updated config back to file const updatedContent = JSON.stringify(config, null, 2); console.log(`\nšŸ” Writing to config file: ${configPath}`); console.log(`šŸ” Content length before: ${configContent.length}`); console.log(`šŸ” Content length after: ${updatedContent.length}`); fs.writeFileSync(configPath, updatedContent); // Verify the file was written const writtenContent = fs.readFileSync(configPath, 'utf8'); console.log(`šŸ” File written successfully. New content length: ${writtenContent.length}`); // Display the message and continue console.log('\nšŸ”„ Configuration updated! Continuing with new rules...'); console.log('āš ļø Files that match the new configuration will be automatically moved.'); } catch (error) { console.error(`āŒ Failed to update config: ${error instanceof Error ? error.message : String(error)}`); throw error; } } /** * Create new folder with pattern * @param configContent The current config content * @param configPath The config file path * @param existingFolders The list of existing folders * @param suggestedPattern The pattern to add * @param resolve The resolve function for the promise * @param predefinedFolderName Optional predefined folder name (for auto-detected folders) * @returns Promise<void> */ async createNewFolderWithPattern(configContent, configPath, existingFolders, suggestedPattern, resolve, predefinedFolderName) { return new Promise((resolve2) => { if (predefinedFolderName) { // Use the predefined folder name directly const cleanFolderName = this.sanitizeFolderName(predefinedFolderName); if (!cleanFolderName) { console.log('āŒ Invalid folder name.'); resolve2(); resolve(); return; } // Check if folder already exists if (existingFolders.includes(cleanFolderName)) { console.log(`āŒ Folder "${cleanFolderName}" already exists.`); resolve2(); resolve(); return; } // Parse the JSON content and add the new folder const config = JSON.parse(configContent); // Add the new folder to the structure if (!config.structure) { config.structure = {}; } config.structure[cleanFolderName] = [suggestedPattern]; console.log(`šŸ“ Created new "${cleanFolderName}" folder with pattern "${suggestedPattern}"`); // Write the updated config back to file const updatedContent = JSON.stringify(config, null, 2); // Write the updated config try { console.log(`\nšŸ” Writing to config file: ${configPath}`); console.log(`šŸ” Content length before: ${configContent.length}`); console.log(`šŸ” Content length after: ${updatedContent.length}`); fs.writeFileSync(configPath, updatedContent); // Verify the file was written const writtenContent = fs.readFileSync(configPath, 'utf8'); console.log(`šŸ” File written successfully. New content length: ${writtenContent.length}`); // Display the message and continue console.log('\nšŸ”„ Configuration updated! Continuing with new rules...'); console.log('āš ļø Files that match the new configuration will be automatically moved.'); } catch (error) { console.error(`āŒ Failed to write config file: ${error instanceof Error ? error.message : String(error)}`); throw error; } // Resolve the promises and continue resolve2(); resolve(); } else { // Ask user for folder name this.rl.question('Enter new folder name: ', async (folderName) => { const cleanFolderName = this.sanitizeFolderName(folderName.trim()); if (!cleanFolderName) { console.log('āŒ Invalid folder name.'); resolve2(); resolve(); return; } // Check if folder already exists if (existingFolders.includes(cleanFolderName)) { console.log(`āŒ Folder "${cleanFolderName}" already exists.`); resolve2(); resolve(); return; } // Parse the JSON content and add the new folder const config = JSON.parse(configContent); // Add the new folder to the structure if (!config.structure) { config.structure = {}; } config.structure[cleanFolderName] = [suggestedPattern]; console.log(`šŸ“ Created new "${cleanFolderName}" folder with pattern "${suggestedPattern}"`); // Write the updated config back to file const updatedContent = JSON.stringify(config, null, 2); // Write the updated config try { console.log(`\nšŸ” Writing to config file: ${configPath}`); console.log(`šŸ” Content length before: ${configContent.length}`); console.log(`šŸ” Content length after: ${updatedContent.length}`); fs.writeFileSync(configPath, updatedContent); // Verify the file was written const writtenContent = fs.readFileSync(configPath, 'utf8'); console.log(`šŸ” File written successfully. New content length: ${writtenContent.length}`); // Display the message and continue console.log('\nšŸ”„ Configuration updated! Continuing with new rules...'); console.log('āš ļø Files that match the new configuration will be automatically moved.'); } catch (error) { console.error(`āŒ Failed to write config file: ${error instanceof Error ? error.message : String(error)}`); throw error; } // Resolve the promises and continue resolve2(); resolve(); }); } }); } /** * Sanitize folder name for file system compatibility * @param folderName The folder name to clean * @returns string The cleaned folder name */ sanitizeFolderName(folderName) { if (!folderName || folderName.trim().length === 0) { return ''; } const cleaned = folderName .replace(/[<>:"/\\|?*]/g, '') // Remove invalid file system characters .replace(/\s+/g, '-') // Replace spaces with hyphens .toLowerCase() // Convert to lowercase .trim(); // Remove leading/trailing whitespace return cleaned; } } exports.AddFilePatternToConfigService = AddFilePatternToConfigService; //# sourceMappingURL=AddFilePatternToConfigService.js.map