Skip to content

Commit ece42d1

Browse files
chore: sync validation scripts to examples
Auto-sync validation scripts from scripts/validation/ to example directories 🤖 Generated by GitHub Actions workflow Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
1 parent 1c1845f commit ece42d1

File tree

12 files changed

+1400
-880
lines changed

12 files changed

+1400
-880
lines changed

examples/nextjs-lefthook-example/scripts/validation/check-as-casts.js

Lines changed: 79 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -5,21 +5,11 @@
55
* Enforces proper type safety without type casting
66
*/
77

8-
const fs = require('fs')
9-
const path = require('path')
10-
const { execSync } = require('child_process')
8+
// ============================================================================
9+
// CONFIGURATION - Customize these settings for your project
10+
// ============================================================================
1111

12-
// Colors for terminal output
13-
const colors = {
14-
red: '\x1b[31m',
15-
yellow: '\x1b[33m',
16-
green: '\x1b[32m',
17-
blue: '\x1b[34m',
18-
reset: '\x1b[0m',
19-
bold: '\x1b[1m',
20-
}
21-
22-
// Patterns to detect 'as' type assertions
12+
// Patterns to detect unsafe 'as' type assertions
2313
const AS_CAST_PATTERNS = [
2414
// Match 'as Type' but not 'as const'
2515
/\bas\s+(?!const\b)[A-Z]\w*/g,
@@ -33,7 +23,7 @@ const AS_CAST_PATTERNS = [
3323
/\)\s*as\s+[A-Z]\w*/g,
3424
]
3525

36-
// Exceptions where 'as' might be acceptable (can be configured)
26+
// Exceptions where 'as' might be acceptable
3727
const ALLOWED_PATTERNS = [
3828
// Allow 'as const' for const assertions
3929
/\bas\s+const\b/g,
@@ -45,14 +35,40 @@ const ALLOWED_PATTERNS = [
4535
/scripts\/migrate.*\.ts$/,
4636
]
4737

38+
// Directories to exclude from checking
39+
const EXCLUDE_PATHS = [
40+
'node_modules',
41+
'dist',
42+
'build',
43+
'.next',
44+
'coverage',
45+
'.git',
46+
'public'
47+
]
48+
49+
// ============================================================================
50+
// IMPLEMENTATION - No need to modify below this line
51+
// ============================================================================
52+
53+
const fs = require('fs')
54+
const path = require('path')
55+
const { execSync } = require('child_process')
56+
57+
// Colors for terminal output
58+
const colors = {
59+
red: '\x1b[31m',
60+
yellow: '\x1b[33m',
61+
green: '\x1b[32m',
62+
blue: '\x1b[34m',
63+
reset: '\x1b[0m',
64+
bold: '\x1b[1m',
65+
}
66+
4867
/**
4968
* Check if a file path should be excluded
5069
*/
5170
function shouldExcludeFile(filePath) {
52-
// Exclude node_modules, build directories, etc.
53-
const excludePaths = ['node_modules', 'dist', 'build', '.next', 'coverage', '.git', 'public']
54-
55-
return excludePaths.some((exclude) => filePath.includes(exclude))
71+
return EXCLUDE_PATHS.some((exclude) => filePath.includes(exclude))
5672
}
5773

5874
/**
@@ -107,6 +123,22 @@ function findAsCasts(filePath, content) {
107123
return violations
108124
}
109125

126+
/**
127+
* Check a specific file for as casts
128+
*/
129+
function checkFileForAsCasts(filePath) {
130+
if (!fs.existsSync(filePath)) {
131+
return []
132+
}
133+
134+
if (shouldExcludeFile(filePath) || isAllowedFile(filePath)) {
135+
return []
136+
}
137+
138+
const content = fs.readFileSync(filePath, 'utf8')
139+
return findAsCasts(filePath, content)
140+
}
141+
110142
/**
111143
* Get list of staged TypeScript files
112144
*/
@@ -231,12 +263,37 @@ if (isFix) {
231263
}
232264

233265
// Get files to check
234-
const files = isFullCheck ? getAllTypeScriptFiles() : getStagedFiles()
266+
let files
267+
const specificFiles = args.filter(arg => !arg.startsWith('--') && (arg.endsWith('.ts') || arg.endsWith('.tsx')))
268+
269+
if (specificFiles.length > 0) {
270+
// Use specific files provided as arguments
271+
files = specificFiles.filter(file => fs.existsSync(file))
272+
} else if (isFullCheck) {
273+
// Check all TypeScript files
274+
files = getAllTypeScriptFiles()
275+
} else {
276+
// Check only staged files (for git hooks)
277+
files = getStagedFiles()
278+
}
235279

236280
if (files.length === 0) {
237281
console.log(`${colors.blue}ℹ️ No TypeScript files to check${colors.reset}`)
238282
process.exit(0)
239283
}
240284

241-
// Run validation
242-
validateFiles(files, !isFullCheck)
285+
// Run validation (only if called directly)
286+
if (require.main === module) {
287+
validateFiles(files, !isFullCheck)
288+
}
289+
290+
// Export for use in ESLint plugin
291+
module.exports = {
292+
AS_CAST_PATTERNS,
293+
ALLOWED_PATTERNS,
294+
EXCLUDE_PATHS,
295+
checkFileForAsCasts,
296+
findAsCasts,
297+
shouldExcludeFile,
298+
isAllowedFile
299+
}
Lines changed: 121 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,97 @@
11
#!/usr/bin/env node
22

3-
const fs = require('fs')
4-
const path = require('path')
5-
6-
const BARREL_FILE_PATTERNS = ['index.ts', 'index.js', 'index.tsx', 'index.jsx']
3+
/**
4+
* Detects barrel files that hurt build performance and tree-shaking
5+
* Prevents re-export patterns that slow down bundlers
6+
*/
7+
8+
// ============================================================================
9+
// CONFIGURATION - Customize these settings for your project
10+
// ============================================================================
11+
12+
// File names that are considered potential barrel files
13+
const BARREL_FILE_PATTERNS = [
14+
'index.ts',
15+
'index.js',
16+
'index.mjs',
17+
'index.cjs',
18+
'index.tsx',
19+
'index.jsx'
20+
]
721

8-
// Directories to exclude from barrel file checks
9-
const EXCLUDED_DIRS = [
22+
// Directories to exclude from barrel file checking
23+
const EXCLUDED_DIRECTORIES = [
1024
'node_modules',
1125
'.next',
12-
'.git',
26+
'.git',
1327
'dist',
1428
'build',
1529
'out',
1630
'coverage',
1731
'.turbo',
32+
'.vercel',
33+
'.netlify',
1834
]
1935

20-
// Specific files to allow (e.g., Next.js pages/index.tsx is legitimate)
21-
const ALLOWED_BARREL_FILES = [
22-
'app/page.tsx', // Next.js 13+ app router root page
23-
'pages/index.tsx', // Next.js pages router root page
36+
// Index files that are legitimate (not barrel files)
37+
const ALLOWED_INDEX_FILES = [
38+
// Next.js legitimate index files
39+
'app/page.tsx', // Next.js 13+ app router root page
40+
'pages/index.tsx', // Next.js pages router root page
2441
'pages/index.js',
42+
'pages/index.mjs',
2543
'src/pages/index.tsx',
2644
'src/pages/index.js',
45+
'src/pages/index.mjs',
46+
47+
// Other legitimate index files
48+
'src/index.ts', // Package entry point
49+
'src/index.js', // Package entry point
50+
'src/index.mjs', // ES module entry point
51+
'src/index.cjs', // CommonJS entry point
52+
'lib/index.ts', // Library entry point
53+
'lib/index.js', // Library entry point
54+
'lib/index.mjs', // Library ES module entry point
55+
]
56+
57+
// Patterns that indicate a barrel file (re-exports)
58+
const BARREL_EXPORT_PATTERNS = [
59+
/export\s*\*\s*from/g, // export * from './module'
60+
/export\s*{\s*.*\s*}\s*from/g, // export { something } from './module'
2761
]
2862

63+
// Minimum number of exports to consider it a barrel file
64+
const MIN_EXPORTS_FOR_BARREL = 3
65+
66+
// ============================================================================
67+
// IMPLEMENTATION - No need to modify below this line
68+
// ============================================================================
69+
70+
const fs = require('fs')
71+
const path = require('path')
72+
73+
// Check if a file is actually a barrel file by analyzing its content
74+
function isBarrelFile(filePath) {
75+
try {
76+
const content = fs.readFileSync(filePath, 'utf8')
77+
let exportCount = 0
78+
79+
// Count barrel export patterns
80+
BARREL_EXPORT_PATTERNS.forEach(pattern => {
81+
const matches = content.match(pattern)
82+
if (matches) {
83+
exportCount += matches.length
84+
}
85+
})
86+
87+
// Consider it a barrel file if it has enough re-exports
88+
return exportCount >= MIN_EXPORTS_FOR_BARREL
89+
} catch (error) {
90+
// If we can't read the file, assume it's not a barrel file
91+
return false
92+
}
93+
}
94+
2995
function findBarrelFiles(dir, basePath = '') {
3096
const barrelFiles = []
3197

@@ -41,16 +107,19 @@ function findBarrelFiles(dir, basePath = '') {
41107

42108
if (entry.isDirectory()) {
43109
// Skip excluded directories
44-
if (EXCLUDED_DIRS.includes(entry.name)) {
110+
if (EXCLUDED_DIRECTORIES.includes(entry.name)) {
45111
continue
46112
}
47113

48114
// Recursively check subdirectories
49115
barrelFiles.push(...findBarrelFiles(fullPath, relativePath))
50116
} else if (entry.isFile() && BARREL_FILE_PATTERNS.includes(entry.name)) {
51117
// Check if this barrel file is in the allowed list
52-
if (!ALLOWED_BARREL_FILES.includes(relativePath)) {
53-
barrelFiles.push(relativePath)
118+
if (!ALLOWED_INDEX_FILES.includes(relativePath)) {
119+
// Check if file contains barrel export patterns
120+
if (isBarrelFile(fullPath)) {
121+
barrelFiles.push(relativePath)
122+
}
54123
}
55124
}
56125
}
@@ -61,18 +130,34 @@ function findBarrelFiles(dir, basePath = '') {
61130
function main() {
62131
console.log('🔍 Checking for barrel files...')
63132

133+
// Parse command line arguments
134+
const args = process.argv.slice(2)
135+
const specificDirs = args.filter(arg => !arg.startsWith('--'))
136+
137+
// Determine directories to search
138+
const searchDirs = specificDirs.length > 0 ? specificDirs : ['.']
139+
140+
// Find barrel files in all specified directories
141+
let allBarrelFiles = []
142+
searchDirs.forEach(dir => {
143+
if (fs.existsSync(dir)) {
144+
const barrelFiles = findBarrelFiles(dir)
145+
allBarrelFiles = allBarrelFiles.concat(barrelFiles)
146+
}
147+
})
148+
64149
// Exclude generated types directory from barrel checks
65-
const barrelFiles = findBarrelFiles('.').filter((p) => !p.startsWith('lib/types/generated'))
150+
allBarrelFiles = allBarrelFiles.filter((p) => !p.startsWith('lib/types/generated'))
66151

67-
if (barrelFiles.length > 0) {
152+
if (allBarrelFiles.length > 0) {
68153
console.log('❌ COMMIT BLOCKED: Barrel files are not allowed!')
69154
console.log('')
70155
console.log('💡 Use explicit imports instead of barrel files:')
71156
console.log(" ❌ import { Component } from '@/components'")
72157
console.log(" ✅ import { Component } from '@/components/component'")
73158
console.log('')
74159
console.log('📋 Found barrel files:')
75-
barrelFiles.forEach((file) => {
160+
allBarrelFiles.forEach((file) => {
76161
console.log(` - ${file}`)
77162
})
78163
console.log('')
@@ -83,7 +168,7 @@ function main() {
83168
console.log(' • Faster builds and IDE performance')
84169
console.log(' • More explicit and maintainable code')
85170
console.log('')
86-
console.log('🛠️ To fix: Remove index.ts files and update imports to be explicit')
171+
console.log('🛠️ To fix: Remove index files and update imports to be explicit')
87172

88173
if (process.env.NODE_ENV !== 'development') {
89174
process.exit(1)
@@ -95,4 +180,21 @@ function main() {
95180
process.exit(0)
96181
}
97182

98-
main()
183+
// Run validation (only if called directly)
184+
if (require.main === module) {
185+
main()
186+
}
187+
188+
// Export for use in ESLint plugin
189+
module.exports = {
190+
BARREL_FILE_PATTERNS,
191+
EXCLUDED_DIRECTORIES,
192+
ALLOWED_INDEX_FILES,
193+
BARREL_EXPORT_PATTERNS,
194+
MIN_EXPORTS_FOR_BARREL,
195+
isBarrelFile,
196+
findAllBarrelFiles: () => {
197+
const barrelFiles = findAllIndexFiles()
198+
return barrelFiles.filter(file => isBarrelFile(file))
199+
}
200+
}

0 commit comments

Comments
 (0)