UNPKG

folder-validator

Version:

A powerful tool to validate and maintain consistent folder structures across your projects

1 lines 12.5 kB
{"version":3,"sources":["../src/index.ts","../src/functions/folder.ts","../src/config/default-folders/index.ts"],"sourcesContent":["// Export functions\nexport * from \"./functions/folder\";\n\n// Export types\nexport * from \"./types\";\n","import fs from \"fs\";\nimport path from \"path\";\nimport {\n FolderDefinition,\n ValidationOptions,\n ValidationResult,\n} from \"../types\";\nimport { defaultFolders } from \"../config/default-folders\";\n\n// Default folders commonly found in Node.js projects\n\n/**\n * Define a folder structure configuration\n * @param folders Array of folder definitions\n * @returns Processed folder configuration\n */\nexport const defineFolders = (\n folders: FolderDefinition[]\n): FolderDefinition[] => {\n return folders.map((folder) => ({\n ...folder,\n required: folder.required ?? false,\n children: folder.children ? defineFolders(folder.children) : undefined,\n }));\n};\n\n/**\n * Dynamically imports the folder config file (TS or JS)\n */\nconst loadUserConfig = async (\n configPath: string\n): Promise<FolderDefinition[]> => {\n if (!fs.existsSync(configPath)) {\n console.warn(\n `Config file not found: ${configPath}. Using default folders only.`\n );\n return [];\n }\n\n const ext = path.extname(configPath);\n\n try {\n if (ext === \".ts\" || ext === \".js\") {\n const { default: config } = await import(\n `file://${path.resolve(configPath)}`\n );\n return defineFolders(config);\n } else {\n throw new Error(\"Unsupported config file format. Use .ts or .js\");\n }\n } catch (error) {\n console.error(\"Error loading config:\", error);\n return [];\n }\n};\n\n/**\n * Reads directory and returns folder names\n */\nconst getFolderStructure = async (dir: string): Promise<string[]> => {\n try {\n const files = await fs.promises.readdir(dir, { withFileTypes: true });\n return files.filter((file) => file.isDirectory()).map((file) => file.name);\n } catch (error) {\n console.error(\"Error reading directory:\", error);\n return [];\n }\n};\n\n/**\n * Check if a folder matches any of the allowed folders or their aliases\n */\nconst isFolderAllowed = (\n folder: string,\n allowedFolders: FolderDefinition[],\n options: ValidationOptions\n): boolean => {\n const compareFn = options.ignoreCase\n ? (a: string, b: string) => a.toLowerCase() === b.toLowerCase()\n : (a: string, b: string) => a === b;\n\n return allowedFolders.some((allowed) => compareFn(folder, allowed.name));\n};\n\n/**\n * Recursively validates a folder structure against allowed folders\n */\nasync function validateFolderRecursive(\n currentPath: string,\n allowedFolders: FolderDefinition[],\n options: ValidationOptions,\n result: ValidationResult,\n parentPath: string = \"\",\n userFoldersMap?: Set<string>\n): Promise<void> {\n const actualFolders = await getFolderStructure(currentPath);\n\n // Check for disallowed folders at current level\n const disallowed = actualFolders.filter(\n (folder) => !isFolderAllowed(folder, allowedFolders, options)\n );\n\n if (disallowed.length > 0) {\n if (options.strict) {\n result.isValid = false;\n const fullPath = parentPath ? `${parentPath}/` : \"\";\n result.errors.push(\n `Disallowed folders found in ${fullPath}: ${disallowed.join(\", \")}`\n );\n } else if (!options.allowUnknown) {\n const fullPath = parentPath ? `${parentPath}/` : \"\";\n result.warnings.push(\n `Unknown folders found in ${fullPath}: ${disallowed.join(\", \")}`\n );\n }\n }\n\n // Check for required folders at current level\n const missingRequired = allowedFolders\n .filter((folder) => folder.required)\n .filter(\n (folder) =>\n !actualFolders.some((actual) =>\n isFolderAllowed(actual, [folder], options)\n )\n );\n\n if (missingRequired.length > 0) {\n result.isValid = false;\n const fullPath = parentPath ? `${parentPath}/` : \"\";\n result.errors.push(\n `Missing required folders in ${fullPath}: ${missingRequired\n .map((f) => f.name)\n .join(\", \")}`\n );\n }\n\n // Check for optional folders that don't exist and add them as reminders\n const missingOptional = allowedFolders\n .filter((folder) => !folder.required)\n .filter((folder) => {\n if (!userFoldersMap) return true;\n const fullPath = parentPath\n ? `${parentPath}/${folder.name}`\n : folder.name;\n return userFoldersMap.has(fullPath);\n })\n .filter(\n (folder) =>\n !actualFolders.some((actual) =>\n isFolderAllowed(actual, [folder], options)\n )\n );\n\n if (missingOptional.length > 0) {\n const fullPath = parentPath ? `${parentPath}/` : \"\";\n const optionalInfo = missingOptional\n .map((f) => {\n const parts = [];\n parts.push(f.name);\n if (f.description) {\n parts.push(f.description);\n }\n return parts.join(\" - \");\n })\n .join(\"\\n • \");\n result.warnings.push(\n `Optional folders that could be added in ${fullPath}:\\n • ${optionalInfo}`\n );\n }\n\n // Recursively check child folders\n for (const folder of actualFolders) {\n const matchingConfig = allowedFolders.find((allowed) =>\n isFolderAllowed(folder, [allowed], options)\n );\n\n if (matchingConfig?.children) {\n const folderPath = path.join(currentPath, folder);\n const newParentPath = parentPath ? `${parentPath}/${folder}` : folder;\n await validateFolderRecursive(\n folderPath,\n matchingConfig.children,\n options,\n result,\n newParentPath,\n userFoldersMap\n );\n }\n }\n}\n\n/**\n * Validates the folder structure against allowed folders\n */\nexport const validateFolderStructure = async (\n rootPath: string,\n configOrPath: string | FolderDefinition[],\n options: ValidationOptions = {}\n): Promise<ValidationResult> => {\n const defaultOptions: ValidationOptions = {\n strict: false,\n allowUnknown: true,\n ignoreCase: false,\n };\n\n const finalOptions = { ...defaultOptions, ...options };\n const result: ValidationResult = {\n isValid: true,\n errors: [],\n warnings: [],\n };\n\n try {\n const userFolders =\n typeof configOrPath === \"string\"\n ? await loadUserConfig(configOrPath)\n : configOrPath;\n const allowedFolders = [...userFolders, ...defaultFolders];\n\n // Store user folders separately for showing optional folders\n const userFoldersMap = new Set<string>();\n const addToMap = (folders: FolderDefinition[], parentPath = \"\") => {\n for (const folder of folders) {\n const fullPath = parentPath\n ? `${parentPath}/${folder.name}`\n : folder.name;\n userFoldersMap.add(fullPath);\n if (folder.children) {\n addToMap(folder.children, fullPath);\n }\n }\n };\n addToMap(userFolders);\n\n await validateFolderRecursive(\n rootPath,\n allowedFolders,\n finalOptions,\n result,\n \"\",\n userFoldersMap\n );\n\n return result;\n } catch (error) {\n result.isValid = false;\n result.errors.push(`Error checking folder structure: ${error}`);\n return result;\n }\n};\n","import { FolderDefinition } from \"../../types/folder\";\n\n// Default folders commonly found in Node.js projects\nexport const defaultFolders: FolderDefinition[] = [\n // Application related folders\n { name: \"dist\", required: false },\n { name: \"build\", required: false },\n\n // Node.js related folders\n { name: \"node_modules\", required: false },\n\n // Git related folders\n { name: \".git\", required: false },\n\n // Development tools folders\n { name: \".next\", required: false },\n { name: \".husky\", required: false },\n { name: \".vscode\", required: false },\n { name: \"coverage\", required: false },\n { name: \".nixpacks\", required: false },\n];\n"],"mappings":"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","names":["index_exports","__export","defineFolders","validateFolderStructure","__toCommonJS","import_fs","import_path","defaultFolders","defineFolders","folders","folder","loadUserConfig","configPath","fs","ext","path","config","error","getFolderStructure","dir","file","isFolderAllowed","allowedFolders","options","compareFn","a","b","allowed","validateFolderRecursive","currentPath","result","parentPath","userFoldersMap","actualFolders","disallowed","fullPath","missingRequired","actual","f","missingOptional","optionalInfo","parts","matchingConfig","folderPath","newParentPath","validateFolderStructure","rootPath","configOrPath","finalOptions","userFolders","defaultFolders","addToMap"]}