1+ import { describe , it , expect , beforeEach , afterEach } from '@jest/globals' ;
2+ import * as fs from 'fs/promises' ;
3+ import * as path from 'path' ;
4+ import * as os from 'os' ;
5+
6+ // We need to test the buildTree function, but it's defined inside the request handler
7+ // So we'll extract the core logic into a testable function
8+ import { minimatch } from 'minimatch' ;
9+
10+ interface TreeEntry {
11+ name : string ;
12+ type : 'file' | 'directory' ;
13+ children ?: TreeEntry [ ] ;
14+ }
15+
16+ async function buildTreeForTesting ( currentPath : string , rootPath : string , excludePatterns : string [ ] = [ ] ) : Promise < TreeEntry [ ] > {
17+ const entries = await fs . readdir ( currentPath , { withFileTypes : true } ) ;
18+ const result : TreeEntry [ ] = [ ] ;
19+
20+ for ( const entry of entries ) {
21+ const relativePath = path . relative ( rootPath , path . join ( currentPath , entry . name ) ) ;
22+ const shouldExclude = excludePatterns . some ( pattern => {
23+ if ( pattern . includes ( '*' ) ) {
24+ return minimatch ( relativePath , pattern , { dot : true } ) ;
25+ }
26+ // For files: match exact name or as part of path
27+ // For directories: match as directory path
28+ return minimatch ( relativePath , pattern , { dot : true } ) ||
29+ minimatch ( relativePath , `**/${ pattern } ` , { dot : true } ) ||
30+ minimatch ( relativePath , `**/${ pattern } /**` , { dot : true } ) ;
31+ } ) ;
32+ if ( shouldExclude )
33+ continue ;
34+
35+ const entryData : TreeEntry = {
36+ name : entry . name ,
37+ type : entry . isDirectory ( ) ? 'directory' : 'file'
38+ } ;
39+
40+ if ( entry . isDirectory ( ) ) {
41+ const subPath = path . join ( currentPath , entry . name ) ;
42+ entryData . children = await buildTreeForTesting ( subPath , rootPath , excludePatterns ) ;
43+ }
44+
45+ result . push ( entryData ) ;
46+ }
47+
48+ return result ;
49+ }
50+
51+ describe ( 'buildTree exclude patterns' , ( ) => {
52+ let testDir : string ;
53+
54+ beforeEach ( async ( ) => {
55+ testDir = await fs . mkdtemp ( path . join ( os . tmpdir ( ) , 'filesystem-test-' ) ) ;
56+
57+ // Create test directory structure
58+ await fs . mkdir ( path . join ( testDir , 'src' ) ) ;
59+ await fs . mkdir ( path . join ( testDir , 'node_modules' ) ) ;
60+ await fs . mkdir ( path . join ( testDir , '.git' ) ) ;
61+ await fs . mkdir ( path . join ( testDir , 'nested' , 'node_modules' ) , { recursive : true } ) ;
62+
63+ // Create test files
64+ await fs . writeFile ( path . join ( testDir , '.env' ) , 'SECRET=value' ) ;
65+ await fs . writeFile ( path . join ( testDir , '.env.local' ) , 'LOCAL_SECRET=value' ) ;
66+ await fs . writeFile ( path . join ( testDir , 'src' , 'index.js' ) , 'console.log("hello");' ) ;
67+ await fs . writeFile ( path . join ( testDir , 'package.json' ) , '{}' ) ;
68+ await fs . writeFile ( path . join ( testDir , 'node_modules' , 'module.js' ) , 'module.exports = {};' ) ;
69+ await fs . writeFile ( path . join ( testDir , 'nested' , 'node_modules' , 'deep.js' ) , 'module.exports = {};' ) ;
70+ } ) ;
71+
72+ afterEach ( async ( ) => {
73+ await fs . rm ( testDir , { recursive : true , force : true } ) ;
74+ } ) ;
75+
76+ it ( 'should exclude files matching simple patterns' , async ( ) => {
77+ // Test the current implementation - this will fail until the bug is fixed
78+ const tree = await buildTreeForTesting ( testDir , testDir , [ '.env' ] ) ;
79+ const fileNames = tree . map ( entry => entry . name ) ;
80+
81+ expect ( fileNames ) . not . toContain ( '.env' ) ;
82+ expect ( fileNames ) . toContain ( '.env.local' ) ; // Should not exclude this
83+ expect ( fileNames ) . toContain ( 'src' ) ;
84+ expect ( fileNames ) . toContain ( 'package.json' ) ;
85+ } ) ;
86+
87+ it ( 'should exclude directories matching simple patterns' , async ( ) => {
88+ const tree = await buildTreeForTesting ( testDir , testDir , [ 'node_modules' ] ) ;
89+ const dirNames = tree . map ( entry => entry . name ) ;
90+
91+ expect ( dirNames ) . not . toContain ( 'node_modules' ) ;
92+ expect ( dirNames ) . toContain ( 'src' ) ;
93+ expect ( dirNames ) . toContain ( '.git' ) ;
94+ } ) ;
95+
96+ it ( 'should exclude nested directories with same pattern' , async ( ) => {
97+ const tree = await buildTreeForTesting ( testDir , testDir , [ 'node_modules' ] ) ;
98+
99+ // Find the nested directory
100+ const nestedDir = tree . find ( entry => entry . name === 'nested' ) ;
101+ expect ( nestedDir ) . toBeDefined ( ) ;
102+ expect ( nestedDir ! . children ) . toBeDefined ( ) ;
103+
104+ // The nested/node_modules should also be excluded
105+ const nestedChildren = nestedDir ! . children ! . map ( child => child . name ) ;
106+ expect ( nestedChildren ) . not . toContain ( 'node_modules' ) ;
107+ } ) ;
108+
109+ it ( 'should handle glob patterns correctly' , async ( ) => {
110+ const tree = await buildTreeForTesting ( testDir , testDir , [ '*.env' ] ) ;
111+ const fileNames = tree . map ( entry => entry . name ) ;
112+
113+ expect ( fileNames ) . not . toContain ( '.env' ) ;
114+ expect ( fileNames ) . toContain ( '.env.local' ) ; // *.env should not match .env.local
115+ expect ( fileNames ) . toContain ( 'src' ) ;
116+ } ) ;
117+
118+ it ( 'should handle dot files correctly' , async ( ) => {
119+ const tree = await buildTreeForTesting ( testDir , testDir , [ '.git' ] ) ;
120+ const dirNames = tree . map ( entry => entry . name ) ;
121+
122+ expect ( dirNames ) . not . toContain ( '.git' ) ;
123+ expect ( dirNames ) . toContain ( '.env' ) ; // Should not exclude this
124+ } ) ;
125+
126+ it ( 'should work with multiple exclude patterns' , async ( ) => {
127+ const tree = await buildTreeForTesting ( testDir , testDir , [ 'node_modules' , '.env' , '.git' ] ) ;
128+ const entryNames = tree . map ( entry => entry . name ) ;
129+
130+ expect ( entryNames ) . not . toContain ( 'node_modules' ) ;
131+ expect ( entryNames ) . not . toContain ( '.env' ) ;
132+ expect ( entryNames ) . not . toContain ( '.git' ) ;
133+ expect ( entryNames ) . toContain ( 'src' ) ;
134+ expect ( entryNames ) . toContain ( 'package.json' ) ;
135+ } ) ;
136+
137+ it ( 'should handle empty exclude patterns' , async ( ) => {
138+ const tree = await buildTreeForTesting ( testDir , testDir , [ ] ) ;
139+ const entryNames = tree . map ( entry => entry . name ) ;
140+
141+ // All entries should be included
142+ expect ( entryNames ) . toContain ( 'node_modules' ) ;
143+ expect ( entryNames ) . toContain ( '.env' ) ;
144+ expect ( entryNames ) . toContain ( '.git' ) ;
145+ expect ( entryNames ) . toContain ( 'src' ) ;
146+ } ) ;
147+ } ) ;
0 commit comments