Source identity
| Field | Recorded value |
|---|---|
| Source type | GitHub source |
| Original record | Janady13/llms-txt-hub / scripts/validation/check-directory-structure.js |
| Date | Not specified in source |
| Repository | Janady13/llms-txt-hub |
| Revision | e0f4bf978d4df168369c7601d9c5f5ea89e84377 |
| Branch | main |
| Path or archive member | scripts/validation/check-directory-structure.js |
| Original SHA-256 | fc4b340ff076b468a253714c07eb745fbf6047dfe381e93462e0895ced875cb8 |
| Git blob SHA | e0fccc716ebd25b34e9074f73a9a068b7fe7c2f8 |
| Reading-copy SHA-256 | fc4b340ff076b468a253714c07eb745fbf6047dfe381e93462e0895ced875cb8 |
Recovered source transcript
Download complete source text ↓265 lines · 7.4 KB
#!/usr/bin/env node
/**
* Validates project directory structure for maintainability
* Enforces consistent organization patterns
*/
// ============================================================================
// CONFIGURATION - Customize these settings for your project
// ============================================================================
// Required top-level directories and their rules
const REQUIRED_DIRECTORIES = [
'src', // Source code
'components' // React components (can be in src/)
]
// Optional directories that have specific structure rules when present
const OPTIONAL_DIRECTORIES = [
'lib', // Utility libraries
'hooks', // Custom React hooks
'types', // TypeScript type definitions
'styles', // CSS/styling files
'utils', // General utilities
'api', // API related code
'features' // Feature-based modules
]
// Component organization patterns (adjust for your team's preference)
const COMPONENT_STRUCTURE = {
allowedSubdirectories: [
'ui', // Basic UI components (Button, Input, etc.) usually for Shadcn/UI
'layout', // Layout components (Header, Footer, etc.)
'forms', // Form-related components
'common', // Shared/common components
'pages' // Page-specific components
],
maxDepth: 3, // Maximum nesting depth for component directories
enforceGrouping: true // Require components to be in subdirectories
}
// Files that are allowed in the root directory
const ALLOWED_ROOT_FILES = [
// Configuration files
'package.json',
'tsconfig.json',
'jsconfig.json',
'next.config.js',
'next.config.mjs',
'next.config.ts',
'tailwind.config.js',
'tailwind.config.ts',
'tailwind.config.mjs',
'postcss.config.js',
'postcss.config.mjs',
'postcss.config.cjs',
'eslint.config.js',
'eslint.config.mjs',
'eslint.config.ts',
'.eslintrc.js',
'.eslintrc.mjs',
'.eslintrc.cjs',
'.eslintrc.json',
'prettier.config.js',
'prettier.config.mjs',
'prettier.config.cjs',
'.prettierrc.js',
'.prettierrc.mjs',
'vite.config.js',
'vite.config.ts',
'vite.config.mjs',
'vitest.config.js',
'vitest.config.ts',
'vitest.config.mjs',
'webpack.config.js',
'webpack.config.mjs',
'rollup.config.js',
'rollup.config.mjs',
// Documentation
'README.md',
'CHANGELOG.md',
'LICENSE',
// Git and CI
'.gitignore',
'.gitattributes',
'lefthook.yml',
'.husky',
// Other
'.env.example',
'.env.local',
'yarn.lock',
'package-lock.json',
'pnpm-lock.yaml'
]
// Directories to exclude from structure checking
const EXCLUDE_DIRECTORIES = [
'node_modules',
'.next',
'.git',
'dist',
'build',
'out',
'coverage',
'.turbo',
'.vercel',
'.netlify'
]
// ============================================================================
// IMPLEMENTATION - No need to modify below this line
// ============================================================================
const fs = require('node:fs')
const path = require('node:path')
// Helper function to check if directory should be excluded
function shouldExcludeDirectory(dirPath) {
return EXCLUDE_DIRECTORIES.some(exclude => dirPath.includes(exclude))
}
// Helper function to validate component directory structure
function validateComponentStructure(componentsDir) {
const warnings = []
const errors = []
try {
// Check for loose component files
const componentFiles = fs
.readdirSync(componentsDir, { withFileTypes: true })
.filter(dirent => dirent.isFile() && /\.(tsx?|jsx?)$/.test(dirent.name))
if (componentFiles.length > 5) {
warnings.push(
`Consider organizing components into subdirectories (found ${componentFiles.length} files in ${componentsDir})`
)
}
// Check subdirectory organization
const subdirs = fs
.readdirSync(componentsDir, { withFileTypes: true })
.filter(dirent => dirent.isDirectory())
.map(dirent => dirent.name)
const unknownSubdirs = subdirs.filter(
dir =>
!COMPONENT_STRUCTURE.allowedSubdirectories.includes(dir) &&
!shouldExcludeDirectory(path.join(componentsDir, dir))
)
if (unknownSubdirs.length > 0) {
warnings.push(
`Unexpected component subdirectories: ${unknownSubdirs.join(', ')}. Consider using: ${COMPONENT_STRUCTURE.allowedSubdirectories.join(', ')}`
)
}
// Check nesting depth
const checkDepth = (dir, currentDepth = 0) => {
if (currentDepth > COMPONENT_STRUCTURE.maxDepth) {
errors.push(
`Component directory nesting too deep in ${dir} (max: ${COMPONENT_STRUCTURE.maxDepth})`
)
return
}
try {
const items = fs.readdirSync(dir, { withFileTypes: true })
items.forEach(item => {
if (item.isDirectory() && !shouldExcludeDirectory(path.join(dir, item.name))) {
checkDepth(path.join(dir, item.name), currentDepth + 1)
}
})
} catch (_error) {
// Skip directories that can't be read
}
}
checkDepth(componentsDir)
} catch (error) {
warnings.push(`Could not validate component structure: ${error.message}`)
}
return { warnings, errors }
}
// Main validation function
function validateDirectoryStructure() {
const errors = []
const warnings = []
try {
// Check if required directories exist
REQUIRED_DIRECTORIES.forEach(dir => {
if (!fs.existsSync(dir) && !fs.existsSync(path.join('src', dir))) {
warnings.push(`Recommended directory missing: ${dir}`)
}
})
// Check for optional directories that have specific rules
OPTIONAL_DIRECTORIES.forEach(dir => {
const dirPath = fs.existsSync(dir)
? dir
: fs.existsSync(path.join('src', dir))
? path.join('src', dir)
: null
if (dirPath && !shouldExcludeDirectory(dirPath)) {
// Optional directories exist and can be validated for structure if needed
// This is where you could add specific validation rules for each directory type
}
})
// Check root directory for unwanted files
const rootFiles = fs
.readdirSync('.', { withFileTypes: true })
.filter(dirent => dirent.isFile())
.map(dirent => dirent.name)
const unwantedFiles = rootFiles.filter(file => !ALLOWED_ROOT_FILES.includes(file))
if (unwantedFiles.length > 0) {
warnings.push(`Consider organizing these root files: ${unwantedFiles.join(', ')}`)
}
// Check component structure if components directory exists
const componentsDir = fs.existsSync('components')
? 'components'
: fs.existsSync('src/components')
? 'src/components'
: null
if (componentsDir && COMPONENT_STRUCTURE.enforceGrouping) {
const result = validateComponentStructure(componentsDir)
warnings.push(...result.warnings)
errors.push(...result.errors)
}
console.log('✅ Directory structure validation completed')
if (warnings.length > 0) {
console.log('⚠️ Warnings:')
warnings.forEach(warning => console.log(` - ${warning}`))
}
if (errors.length > 0) {
console.log('❌ Errors:')
errors.forEach(error => console.log(` - ${error}`))
}
return errors.length === 0
} catch (error) {
console.error('❌ Directory structure validation failed:', error.message)
return false
}
}
// Run validation if called directly
if (require.main === module) {
const success = validateDirectoryStructure()
process.exit(success ? 0 : 1)
}
module.exports = { validateDirectoryStructure }