filetree-pro
Version:
A powerful file tree generator for VS Code and Cursor. Generate beautiful file trees in multiple formats with smart exclusions and custom configurations.
773 lines (627 loc) ⢠19.2 kB
Markdown
# šļø FileTree Pro - Architecture Improvement Proposal
**Version:** 1.0
**Date:** October 17, 2025
**Status:** Proposed
---
## š Overview
This document proposes architectural improvements to make FileTree Pro more scalable, maintainable, and production-ready.
---
## šÆ Current vs. Proposed Architecture
### Current Architecture (As-Is)
```mermaid
flowchart TB
subgraph "Current State"
EXT[Extension<br/>Partially initialized]
CMD[Commands<br/>1100+ lines]
subgraph "Services"
FS[FileSystemService<br/>No cache mgmt]
CP[CopilotService<br/>No rate limit]
AN[AnalyticsService]
end
FTP[FileTreeProvider<br/>Not registered]
end
EXT -.->|Missing| FTP
CMD -->|Duplicate logic| FS
style EXT fill:#f44336,color:#fff
style CMD fill:#ff9800,color:#000
style FS fill:#ff9800,color:#000
style CP fill:#ff9800,color:#000
style FTP fill:#f44336,color:#fff
```
### Proposed Architecture (To-Be)
```mermaid
flowchart TB
subgraph "VS Code Extension"
EXT[Extension Entry<br/>Properly initialized]
subgraph "Command Layer"
GEN[GenerateTreeCommand]
CONV[ConvertTextCommand]
EXP[ExportCommand]
end
subgraph "Service Layer"
FS[FileSystemService<br/>+ Cache Management<br/>+ Path Validation]
CP[CopilotService<br/>+ Rate Limiting<br/>+ Size Validation]
AN[AnalyticsService<br/>+ Performance Metrics]
SEC[SecurityService<br/>NEW]
end
subgraph "Provider Layer"
FTP[FileTreeProvider<br/>+ Lazy Loading<br/>Registered]
end
subgraph "Formatter Layer"
MD[MarkdownFormatter]
JSON[JSONFormatter]
SVG[SVGFormatter]
ASCII[ASCIIFormatter]
end
subgraph "Utility Layer"
FU[FileUtils<br/>Single icon map]
ERR[ErrorHandler<br/>NEW]
CACHE[CacheManager<br/>NEW]
end
end
EXT --> GEN
EXT --> CONV
EXT --> EXP
EXT --> FTP
GEN --> FS
GEN --> MD
GEN --> JSON
GEN --> SVG
GEN --> ASCII
FTP --> FS
FTP --> CP
FTP --> AN
FS --> SEC
FS --> CACHE
FS --> FU
CP --> SEC
CP --> CACHE
GEN -.->|Uses| ERR
FS -.->|Uses| ERR
CP -.->|Uses| ERR
style EXT fill:#4CAF50,color:#fff
style GEN fill:#2196F3,color:#fff
style FS fill:#4CAF50,color:#fff
style CP fill:#4CAF50,color:#fff
style FTP fill:#4CAF50,color:#fff
style SEC fill:#4CAF50,color:#fff
style ERR fill:#4CAF50,color:#fff
style CACHE fill:#4CAF50,color:#fff
```
---
## š¦ Proposed Module Structure
### Before (Current)
```
src/
āāā extension.ts (45 lines)
āāā extension-simple.ts (13 lines) ā Unused
āāā types.ts (165 lines)
āāā commands/
ā āāā commands.ts (1106 lines) ā ļø Too large!
āāā providers/
ā āāā fileTreeProvider.ts (227 lines)
āāā services/
ā āāā fileSystemService.ts (242 lines)
ā āāā copilotService.ts (239 lines)
ā āāā analyticsService.ts (276 lines)
āāā utils/
āāā fileUtils.ts (287 lines)
```
### After (Proposed)
```
src/
āāā extension.ts (100 lines) ā
Properly initialized
āāā types/
ā āāā index.ts (Export all types)
ā āāā fileTree.types.ts
ā āāā service.types.ts
ā āāā config.types.ts
āāā commands/
ā āāā index.ts (Export all commands)
ā āāā generateTreeCommand.ts (150 lines)
ā āāā convertTextCommand.ts (80 lines)
ā āāā exportCommand.ts (100 lines)
āāā formatters/
ā āāā index.ts (Export all formatters)
ā āāā markdownFormatter.ts (200 lines)
ā āāā jsonFormatter.ts (150 lines)
ā āāā svgFormatter.ts (300 lines)
ā āāā asciiFormatter.ts (150 lines)
āāā providers/
ā āāā fileTreeProvider.ts (250 lines) ā
Enhanced
āāā services/
ā āāā fileSystemService.ts (300 lines) ā
Enhanced
ā āāā copilotService.ts (280 lines) ā
Enhanced
ā āāā analyticsService.ts (280 lines)
ā āāā securityService.ts (150 lines) ⨠NEW
āāā utils/
ā āāā fileUtils.ts (300 lines)
ā āāā errorHandler.ts (100 lines) ⨠NEW
ā āāā cacheManager.ts (150 lines) ⨠NEW
ā āāā securityUtils.ts (100 lines) ⨠NEW
āāā __tests__/
āāā unit/
ā āāā services.test.ts
ā āāā commands.test.ts
ā āāā utils.test.ts
āāā integration/
ā āāā extension.test.ts
āāā fixtures/
āāā sample-projects/
```
---
## š Data Flow Improvements
### Current Data Flow (Problematic)
```mermaid
sequenceDiagram
participant User
participant Extension
participant Commands
participant FileSystem
participant VSCode
User->>Extension: Right-click folder
Extension->>Commands: Execute command
Commands->>FileSystem: Read directory (sync)
FileSystem->>VSCode: fs.readFileSync() ā
VSCode-->>FileSystem: File content
FileSystem-->>Commands: Tree data
Commands-->>User: Display tree
Note over Commands,FileSystem: ā ļø No caching
Note over FileSystem: ā ļø No validation
Note over Commands: ā ļø Duplicate logic
```
### Proposed Data Flow (Improved)
```mermaid
sequenceDiagram
participant User
participant Extension
participant Command
participant Security
participant Cache
participant FileSystem
participant Formatter
participant VSCode
User->>Extension: Right-click folder
Extension->>Command: Execute generateTree
Command->>Security: Validate path
Security-->>Command: ā
Valid
Command->>Cache: Check cache
alt Cache Hit
Cache-->>Command: Cached data
else Cache Miss
Command->>FileSystem: Read directory (async)
FileSystem->>VSCode: workspace.fs.readDirectory()
VSCode-->>FileSystem: Entries
FileSystem->>Cache: Store result
Cache-->>Command: Fresh data
end
Command->>Formatter: Format tree
Formatter-->>Command: Formatted output
Command-->>User: Display tree
Note over Security: ā
Path validation
Note over Cache: ā
TTL + LRU
Note over FileSystem: ā
Async operations
```
---
## š”ļø Security Layer Architecture
```mermaid
flowchart TB
subgraph "Security Service"
VAL[Input Validator]
RATE[Rate Limiter]
SIZE[Size Checker]
PATH[Path Validator]
end
subgraph "Protected Operations"
READ[File Reading]
COPILOT[Copilot Calls]
PATTERN[Pattern Matching]
end
VAL --> READ
RATE --> COPILOT
SIZE --> READ
PATH --> READ
PATH --> PATTERN
style VAL fill:#4CAF50
style RATE fill:#4CAF50
style SIZE fill:#4CAF50
style PATH fill:#4CAF50
```
**Implementation:**
```typescript
// src/services/securityService.ts
export class SecurityService {
private rateLimiter = new RateLimiter();
async validateOperation(
operation: 'read' | 'analyze' | 'pattern',
context: OperationContext
): Promise<ValidationResult> {
switch (operation) {
case 'read':
return this.validateFileRead(context);
case 'analyze':
return this.validateAnalysis(context);
case 'pattern':
return this.validatePattern(context);
}
}
private validateFileRead(context: OperationContext): ValidationResult {
// 1. Check path is in workspace
if (!this.isPathInWorkspace(context.uri)) {
return { valid: false, reason: 'Path outside workspace' };
}
// 2. Check file size
if (context.size && context.size > this.MAX_FILE_SIZE) {
return { valid: false, reason: 'File too large' };
}
return { valid: true };
}
private validateAnalysis(context: OperationContext): ValidationResult {
// 1. Check rate limit
if (!this.rateLimiter.checkLimit('copilot')) {
return { valid: false, reason: 'Rate limit exceeded' };
}
// 2. Check file size
if (context.size && context.size > this.MAX_ANALYSIS_SIZE) {
return { valid: false, reason: 'File too large for analysis' };
}
this.rateLimiter.recordCall('copilot');
return { valid: true };
}
}
```
---
## š¾ Cache Management Architecture
```mermaid
classDiagram
class CacheManager {
-cache: Map~string, CacheEntry~
-maxSize: number
-ttl: number
+get(key: string): T | null
+set(key: string, value: T): void
+invalidate(key: string): void
+clear(): void
+cleanup(): void
}
class CacheEntry~T~ {
+data: T
+timestamp: number
+hits: number
+size: number
}
class CacheStrategy {
<<interface>>
+shouldEvict(entry: CacheEntry): boolean
}
class LRUStrategy {
+shouldEvict(entry: CacheEntry): boolean
}
class TTLStrategy {
+shouldEvict(entry: CacheEntry): boolean
}
CacheManager --> CacheEntry
CacheManager --> CacheStrategy
CacheStrategy <|.. LRUStrategy
CacheStrategy <|.. TTLStrategy
```
**Implementation:**
```typescript
// src/utils/cacheManager.ts
export class CacheManager<T> {
private cache = new Map<string, CacheEntry<T>>();
private readonly maxSize: number;
private readonly ttl: number;
constructor(config: CacheConfig) {
this.maxSize = config.maxSize || 1000;
this.ttl = config.ttl || 5 * 60 * 1000; // 5 minutes
// Auto-cleanup every minute
setInterval(() => this.cleanup(), 60000);
}
get(key: string): T | null {
const entry = this.cache.get(key);
if (!entry) {
return null;
}
// Check TTL
if (Date.now() - entry.timestamp > this.ttl) {
this.cache.delete(key);
return null;
}
// Update hit count
entry.hits++;
return entry.data;
}
set(key: string, value: T): void {
// Enforce size limit
if (this.cache.size >= this.maxSize) {
this.evictLRU();
}
this.cache.set(key, {
data: value,
timestamp: Date.now(),
hits: 0,
size: this.estimateSize(value),
});
}
private evictLRU(): void {
// Find entry with lowest hit count
let minHits = Infinity;
let keyToEvict: string | null = null;
for (const [key, entry] of this.cache.entries()) {
if (entry.hits < minHits) {
minHits = entry.hits;
keyToEvict = key;
}
}
if (keyToEvict) {
this.cache.delete(keyToEvict);
}
}
cleanup(): void {
const now = Date.now();
for (const [key, entry] of this.cache.entries()) {
if (now - entry.timestamp > this.ttl) {
this.cache.delete(key);
}
}
}
getStats(): CacheStats {
let totalSize = 0;
let totalHits = 0;
for (const entry of this.cache.values()) {
totalSize += entry.size;
totalHits += entry.hits;
}
return {
entries: this.cache.size,
totalSize,
totalHits,
hitRate: totalHits / Math.max(1, this.cache.size),
};
}
}
```
---
## šØ Formatter Pattern Implementation
```mermaid
classDiagram
class TreeFormatter {
<<interface>>
+format(tree: FileTreeItem[]): string
+getExtension(): string
}
class MarkdownFormatter {
+format(tree: FileTreeItem[]): string
+getExtension(): string
-generateTreeLines(items, level): string
}
class JSONFormatter {
+format(tree: FileTreeItem[]): string
+getExtension(): string
-buildTreeData(items): object
}
class SVGFormatter {
+format(tree: FileTreeItem[]): string
+getExtension(): string
-renderNode(node, x, y): string
}
class ASCIIFormatter {
+format(tree: FileTreeItem[]): string
+getExtension(): string
-generateAsciiLines(items, prefix): string
}
class FormatterFactory {
+createFormatter(type: string): TreeFormatter
}
TreeFormatter <|.. MarkdownFormatter
TreeFormatter <|.. JSONFormatter
TreeFormatter <|.. SVGFormatter
TreeFormatter <|.. ASCIIFormatter
FormatterFactory --> TreeFormatter
```
**Implementation:**
```typescript
// src/formatters/treeFormatter.interface.ts
export interface TreeFormatter {
format(tree: FileTreeItem[], options: FormatOptions): string;
getExtension(): string;
getLanguageId(): string;
}
// src/formatters/formatterFactory.ts
export class FormatterFactory {
private formatters = new Map<string, TreeFormatter>();
constructor() {
this.formatters.set('markdown', new MarkdownFormatter());
this.formatters.set('json', new JSONFormatter());
this.formatters.set('svg', new SVGFormatter());
this.formatters.set('ascii', new ASCIIFormatter());
}
createFormatter(type: string): TreeFormatter {
const formatter = this.formatters.get(type.toLowerCase());
if (!formatter) {
throw new Error(`Unknown formatter type: ${type}`);
}
return formatter;
}
getAvailableFormats(): string[] {
return Array.from(this.formatters.keys());
}
}
// Usage in command
const factory = new FormatterFactory();
const formatter = factory.createFormatter(formatChoice.value);
const output = formatter.format(tree, options);
```
---
## š Dependency Injection Container
```mermaid
classDiagram
class ServiceContainer {
-services: Map~string, any~
+register~T~(name: string, factory: Factory~T~): void
+resolve~T~(name: string): T
+singleton~T~(name: string, factory: Factory~T~): void
}
class Extension {
-container: ServiceContainer
+activate(context): void
}
class FileTreeProvider {
-fileSystemService: FileSystemService
-copilotService: CopilotService
-analyticsService: AnalyticsService
+constructor(services: Services)
}
Extension --> ServiceContainer
ServiceContainer --> FileTreeProvider
ServiceContainer --> FileSystemService
ServiceContainer --> CopilotService
```
**Implementation:**
```typescript
// src/core/serviceContainer.ts
export class ServiceContainer {
private services = new Map<string, any>();
private singletons = new Map<string, any>();
register<T>(name: string, factory: () => T): void {
this.services.set(name, factory);
}
singleton<T>(name: string, factory: () => T): void {
this.singletons.set(name, factory);
}
resolve<T>(name: string): T {
// Check singleton first
if (this.singletons.has(name)) {
const factory = this.singletons.get(name);
if (!this.services.has(`singleton:${name}`)) {
this.services.set(`singleton:${name}`, factory());
}
return this.services.get(`singleton:${name}`);
}
// Create new instance
const factory = this.services.get(name);
if (!factory) {
throw new Error(`Service not registered: ${name}`);
}
return factory();
}
}
// Usage in extension.ts
export function activate(context: vscode.ExtensionContext) {
const container = new ServiceContainer();
// Register services as singletons
container.singleton('fileSystemService', () => new FileSystemService());
container.singleton('copilotService', () => new CopilotService());
container.singleton('analyticsService', () => new AnalyticsService());
container.singleton('securityService', () => new SecurityService());
container.singleton('cacheManager', () => new CacheManager({ maxSize: 1000, ttl: 300000 }));
// Register provider
container.register('fileTreeProvider', () => {
return new FileTreeProvider(
container.resolve('fileSystemService'),
container.resolve('copilotService'),
container.resolve('analyticsService')
);
});
// Create and register Tree View
const provider = container.resolve<FileTreeProvider>('fileTreeProvider');
const treeView = vscode.window.createTreeView('fileTreeView', {
treeDataProvider: provider,
});
context.subscriptions.push(treeView);
}
```
---
## š Performance Monitoring
```mermaid
flowchart LR
subgraph "Performance Monitoring"
TIMER[Operation Timer]
MEM[Memory Monitor]
CACHE[Cache Stats]
METRICS[Metrics Collector]
end
subgraph "Operations"
READ[File Read]
GEN[Tree Generation]
FORMAT[Formatting]
RENDER[Rendering]
end
READ --> TIMER
GEN --> TIMER
FORMAT --> TIMER
RENDER --> TIMER
TIMER --> METRICS
MEM --> METRICS
CACHE --> METRICS
METRICS --> OUTPUT[Output Channel]
METRICS --> TELEMETRY[Telemetry]
```
**Implementation:**
```typescript
// src/utils/performanceMonitor.ts
export class PerformanceMonitor {
private metrics = new Map<string, OperationMetrics>();
startOperation(name: string): PerformanceTimer {
return new PerformanceTimer(name, this);
}
recordOperation(name: string, duration: number, memoryDelta: number): void {
const existing = this.metrics.get(name) || {
count: 0,
totalDuration: 0,
avgDuration: 0,
minDuration: Infinity,
maxDuration: 0,
totalMemory: 0,
};
existing.count++;
existing.totalDuration += duration;
existing.avgDuration = existing.totalDuration / existing.count;
existing.minDuration = Math.min(existing.minDuration, duration);
existing.maxDuration = Math.max(existing.maxDuration, duration);
existing.totalMemory += memoryDelta;
this.metrics.set(name, existing);
}
getReport(): PerformanceReport {
const report: PerformanceReport = {
operations: [],
totalOperations: 0,
totalTime: 0,
};
for (const [name, metrics] of this.metrics.entries()) {
report.operations.push({ name, ...metrics });
report.totalOperations += metrics.count;
report.totalTime += metrics.totalDuration;
}
return report;
}
}
class PerformanceTimer {
private startTime: number;
private startMemory: number;
constructor(
private name: string,
private monitor: PerformanceMonitor
) {
this.startTime = performance.now();
this.startMemory = process.memoryUsage().heapUsed;
}
end(): void {
const duration = performance.now() - this.startTime;
const memoryDelta = process.memoryUsage().heapUsed - this.startMemory;
this.monitor.recordOperation(this.name, duration, memoryDelta);
}
}
// Usage
const monitor = new PerformanceMonitor();
async function generateTree() {
const timer = monitor.startOperation('generateTree');
try {
// ... tree generation logic
} finally {
timer.end();
}
}
```
---