| 549 | const rootPath = args.path; |
| 550 | |
| 551 | async function buildTree(currentPath: string, excludePatterns: string[] = []): Promise<TreeEntry[]> { |
| 552 | const validPath = await validatePath(currentPath); |
| 553 | const entries = await fs.readdir(validPath, { withFileTypes: true }); |
| 554 | const result: TreeEntry[] = []; |
| 555 | |
| 556 | for (const entry of entries) { |
| 557 | const relativePath = path.relative(rootPath, path.join(currentPath, entry.name)); |
| 558 | const shouldExclude = excludePatterns.some(pattern => { |
| 559 | if (pattern.includes('*')) { |
| 560 | return minimatch(relativePath, pattern, { dot: true }); |
| 561 | } |
| 562 | // For files: match exact name or as part of path |
| 563 | // For directories: match as directory path |
| 564 | return minimatch(relativePath, pattern, { dot: true }) || |
| 565 | minimatch(relativePath, `**/${pattern}`, { dot: true }) || |
| 566 | minimatch(relativePath, `**/${pattern}/**`, { dot: true }); |
| 567 | }); |
| 568 | if (shouldExclude) |
| 569 | continue; |
| 570 | |
| 571 | const entryData: TreeEntry = { |
| 572 | name: entry.name, |
| 573 | type: entry.isDirectory() ? 'directory' : 'file' |
| 574 | }; |
| 575 | |
| 576 | if (entry.isDirectory()) { |
| 577 | const subPath = path.join(currentPath, entry.name); |
| 578 | entryData.children = await buildTree(subPath, excludePatterns); |
| 579 | } |
| 580 | |
| 581 | result.push(entryData); |
| 582 | } |
| 583 | |
| 584 | return result; |
| 585 | } |
| 586 | |
| 587 | const treeData = await buildTree(rootPath, args.excludePatterns); |
| 588 | const text = JSON.stringify(treeData, null, 2); |