@@ -32,6 +32,11 @@ export interface ScannedFile {
3232export interface FileScannerOptions {
3333 /** Root directory to scan (must exist and be a directory). */
3434 readonly rootDir : string
35+ /**
36+ * Glob patterns a file must match at least one of to be included.
37+ * When omitted or empty, all discovered source extensions are included.
38+ */
39+ readonly include ?: readonly string [ ]
3540 /** Additional glob patterns to exclude (merged with defaults). */
3641 readonly extraIgnore ?: readonly string [ ]
3742 /** When true (default), merge patterns from .gitignore files found during walk. */
@@ -52,7 +57,7 @@ export class FileScannerError extends Error {
5257
5358// ─── Constants ────────────────────────────────────────────────────────────────
5459
55- const SOURCE_EXTENSIONS = new Set ( [ '.ts' , '.tsx' , '.js' , '.jsx' ] )
60+ const SOURCE_EXTENSIONS = new Set ( [ '.ts' , '.tsx' , '.js' , '.jsx' , '.mts' , '.cts' ] )
5661
5762const DEFAULT_IGNORED_DIRS = new Set ( [
5863 'node_modules' ,
@@ -68,7 +73,10 @@ const DEFAULT_IGNORED_DIRS = new Set([
6873// ─── Public API ───────────────────────────────────────────────────────────────
6974
7075/**
71- * Recursively discover .ts, .tsx, .js, and .jsx files under rootDir.
76+ * Recursively discover .ts, .tsx, .js, .jsx, .mts, and .cts files under rootDir.
77+ *
78+ * When `include` patterns are provided, a file must match at least one to be
79+ * returned. Exclude patterns reject files that match any ignore glob.
7280 *
7381 * Non-fatal errors (permission denied on a subdirectory) warn and continue.
7482 * Fatal errors (root unreadable) return err().
@@ -80,10 +88,19 @@ export async function scanFiles(
8088 const logger = options . logger ?? noopLogger
8189 const respectGitignore = options . respectGitignore !== false
8290 const ignorePatterns = [ ...( options . extraIgnore ?? [ ] ) ]
91+ const includePatterns = [ ...( options . include ?? [ ] ) ]
8392
8493 try {
8594 const files : ScannedFile [ ] = [ ]
86- await walkDir ( rootDir , rootDir , ignorePatterns , respectGitignore , logger , files )
95+ await walkDir (
96+ rootDir ,
97+ rootDir ,
98+ ignorePatterns ,
99+ includePatterns ,
100+ respectGitignore ,
101+ logger ,
102+ files ,
103+ )
87104
88105 if ( files . length === 0 ) {
89106 logger . info ( 'No matching source files found' , { rootDir } )
@@ -101,6 +118,7 @@ async function walkDir(
101118 dir : string ,
102119 rootDir : string ,
103120 ignorePatterns : readonly string [ ] ,
121+ includePatterns : readonly string [ ] ,
104122 respectGitignore : boolean ,
105123 logger : Logger ,
106124 accumulator : ScannedFile [ ] ,
@@ -146,7 +164,15 @@ async function walkDir(
146164 if ( entry . isDirectory ( ) ) {
147165 if ( DEFAULT_IGNORED_DIRS . has ( entry . name ) ) continue
148166 if ( isIgnoredPath ( relativePath , localIgnorePatterns , true ) ) continue
149- await walkDir ( absolutePath , rootDir , localIgnorePatterns , respectGitignore , logger , accumulator )
167+ await walkDir (
168+ absolutePath ,
169+ rootDir ,
170+ localIgnorePatterns ,
171+ includePatterns ,
172+ respectGitignore ,
173+ logger ,
174+ accumulator ,
175+ )
150176 continue
151177 }
152178
@@ -155,6 +181,7 @@ async function walkDir(
155181
156182 const extension = extname ( relativePath ) . toLowerCase ( )
157183 if ( ! SOURCE_EXTENSIONS . has ( extension ) ) continue
184+ if ( ! isIncludedPath ( relativePath , includePatterns ) ) continue
158185
159186 accumulator . push ( {
160187 absolutePath,
@@ -190,6 +217,11 @@ function isIgnoredPath(
190217 return false
191218}
192219
220+ function isIncludedPath ( relativePath : string , includePatterns : readonly string [ ] ) : boolean {
221+ if ( includePatterns . length === 0 ) return true
222+ return matchesAnyGlob ( relativePath , includePatterns )
223+ }
224+
193225function normalizeRelativePath ( path : string ) : string {
194226 return path . replace ( / \\ / g, '/' )
195227}
0 commit comments