Ejemplos de Operaciones de Archivo Web TypeScript

Ejemplos de operaciones de archivo Web TypeScript incluyendo lectura/escritura de archivos de texto, copiar/mover, recorrido de directorio y validación de archivos

💻 Lectura/Escritura de Archivos de Texto typescript

🟢 simple ⭐⭐

Leer y escribir archivos de texto con varias opciones de codificación usando File API y Blob

⏱️ 20 min 🏷️ typescript, web, file operations
Prerequisites: Basic TypeScript knowledge, File API
// Web TypeScript Text File Read/Write Examples
// Using File API, Blob API, and FileReader for browser-based file operations

// 1. Reading Text Files
class TextFileReader {
  // Read file as text
  async readAsText(file: File): Promise<string> {
    return new Promise((resolve, reject) => {
      const reader = new FileReader();

      reader.onload = () => {
        resolve(reader.result as string);
      };

      reader.onerror = () => {
        reject(new Error('Failed to read file'));
      };

      reader.readAsText(file);
    });
  }

  // Read file with specific encoding
  async readAsTextWithEncoding(file: File, encoding: string): Promise<string> {
    return new Promise((resolve, reject) => {
      const reader = new FileReader();

      reader.onload = () => {
        resolve(reader.result as string);
      };

      reader.onerror = () => {
        reject(new Error('Failed to read file'));
      };

      reader.readAsText(file, encoding);
    });
  }

  // Read file as data URL
  async readAsDataURL(file: File): Promise<string> {
    return new Promise((resolve, reject) => {
      const reader = new FileReader();

      reader.onload = () => {
        resolve(reader.result as string);
      };

      reader.onerror = () => {
        reject(new Error('Failed to read file'));
      };

      reader.readAsDataURL(file);
    });
  }

  // Read file as ArrayBuffer
  async readAsArrayBuffer(file: File): Promise<ArrayBuffer> {
    return new Promise((resolve, reject) => {
      const reader = new FileReader();

      reader.onload = () => {
        resolve(reader.result as ArrayBuffer);
      };

      reader.onerror = () => {
        reject(new Error('Failed to read file'));
      };

      reader.readAsArrayBuffer(file);
    });
  }

  // Read file line by line
  async *readLines(file: File): AsyncGenerator<string> {
    const text = await this.readAsText(file);
    const lines = text.split('\n');

    for (const line of lines) {
      yield line;
    }
  }

  // Read file with chunking (for large files)
  async *readChunks(file: File, chunkSize: number = 1024 * 1024): AsyncGenerator<Blob> {
    const fileSize = file.size;
    let offset = 0;

    while (offset < fileSize) {
      const end = Math.min(offset + chunkSize, fileSize);
      const chunk = file.slice(offset, end);
      yield chunk;
      offset = end;
    }
  }
}

// 2. Writing Text Files
class TextFileWriter {
  // Write text to file (triggers download)
  writeText(fileName: string, text: string, mimeType: string = 'text/plain'): void {
    const blob = new Blob([text], { type: mimeType });
    this.downloadBlob(blob, fileName);
  }

  // Write text lines to file
  writeLines(fileName: string, lines: string[]): void {
    const text = lines.join('\n');
    this.writeText(fileName, text);
  }

  // Write text with specific encoding
  writeTextWithEncoding(fileName: string, text: string, encoding: string): void {
    const encoder = new TextEncoder();
    const uint8Array = encoder.encode(text);
    const blob = new Blob([uint8Array], { type: `text/plain;charset=${encoding}` });
    this.downloadBlob(blob, fileName);
  }

  // Append text to file (returns new blob)
  appendToBlob(existingBlob: Blob, text: string): Blob {
    return new Blob([existingBlob, text], { type: existingBlob.type });
  }

  // Download blob as file
  private downloadBlob(blob: Blob, fileName: string): void {
    const url = URL.createObjectURL(blob);
    const link = document.createElement('a');
    link.href = url;
    link.download = fileName;
    document.body.appendChild(link);
    link.click();
    document.body.removeChild(link);
    URL.revokeObjectURL(url);
  }
}

// 3. File Info Helper
class FileInfoHelper {
  // Get file size in readable format
  getReadableSize(bytes: number): string {
    const kb = bytes / 1024;
    const mb = kb / 1024;
    const gb = mb / 1024;

    if (gb >= 1) {
      return `${gb.toFixed(2)} GB`;
    } else if (mb >= 1) {
      return `${mb.toFixed(2)} MB`;
    } else if (kb >= 1) {
      return `${kb.toFixed(2)} KB`;
    } else {
      return `${bytes} bytes`;
    }
  }

  // Get file extension
  getExtension(file: File): string {
    const name = file.name;
    const lastDotIndex = name.lastIndexOf('.');
    return lastDotIndex !== -1 ? name.substring(lastDotIndex + 1) : '';
  }

  // Get file name without extension
  getFileNameWithoutExtension(file: File): string {
    const name = file.name;
    const lastDotIndex = name.lastIndexOf('.');
    return lastDotIndex !== -1 ? name.substring(0, lastDotIndex) : name;
  }

  // Get MIME type
  getMimeType(file: File): string {
    return file.type || 'application/octet-stream';
  }

  // Get file info object
  getFileDetails(file: File): Record<string, any> {
    return {
      name: file.name,
      size: file.size,
      readableSize: this.getReadableSize(file.size),
      type: file.type,
      lastModified: new Date(file.lastModified),
      extension: this.getExtension(file),
      nameWithoutExtension: this.getFileNameWithoutExtension(file)
    };
  }

  // Print file info
  printFileInfo(file: File): void {
    const info = this.getFileDetails(file);
    console.log('=== File Information ===');
    console.log(`Name: ${info.name}`);
    console.log(`Size: ${info.readableSize}`);
    console.log(`Type: ${info.type}`);
    console.log(`Extension: ${info.extension}`);
    console.log(`Last Modified: ${info.lastModified}`);
  }
}

// 4. File Selection Helper
class FileSelector {
  // Select single file
  async selectFile(acceptType: string = '*'): Promise<File> {
    return new Promise((resolve, reject) => {
      const input = document.createElement('input');
      input.type = 'file';
      input.accept = acceptType;

      input.onchange = (e) => {
        const files = (e.target as HTMLInputElement).files;
        if (files && files.length > 0) {
          resolve(files[0]);
        } else {
          reject(new Error('No file selected'));
        }
      };

      input.oncancel = () => {
        reject(new Error('File selection cancelled'));
      };

      input.click();
    });
  }

  // Select multiple files
  async selectMultipleFiles(acceptType: string = '*'): Promise<File[]> {
    return new Promise((resolve, reject) => {
      const input = document.createElement('input');
      input.type = 'file';
      input.accept = acceptType;
      input.multiple = true;

      input.onchange = (e) => {
        const files = (e.target as HTMLInputElement).files;
        if (files && files.length > 0) {
          resolve(Array.from(files));
        } else {
          reject(new Error('No files selected'));
        }
      };

      input.oncancel = () => {
        reject(new Error('File selection cancelled'));
      };

      input.click();
    });
  }

  // Select files by MIME type
  async selectByMimeType(mimeType: string): Promise<File[]> {
    return this.selectMultipleFiles(mimeType);
  }
}

// 5. File Validation
class FileValidator {
  // Validate file type
  isValidType(file: File, allowedTypes: string[]): boolean {
    return allowedTypes.some(type => file.type === type);
  }

  // Validate file extension
  isValidExtension(file: File, allowedExtensions: string[]): boolean {
    const extension = file.name.split('.').pop()?.toLowerCase();
    return extension !== undefined && allowedExtensions.includes(extension);
  }

  // Validate file size
  isValidSize(file: File, maxSizeInBytes: number): boolean {
    return file.size <= maxSizeInBytes;
  }

  // Validate text file
  isTextFile(file: File): boolean {
    const textTypes = ['text/plain', 'text/html', 'text/css', 'text/javascript',
                      'application/json', 'application/xml'];
    return this.isValidType(file, textTypes);
  }

  // Validate and throw error
  validate(file: File, options: {
    maxSize?: number;
    allowedTypes?: string[];
    allowedExtensions?: string[];
  }): void {
    if (options.maxSize && !this.isValidSize(file, options.maxSize)) {
      throw new Error(`File size exceeds maximum size of ${options.maxSize} bytes`);
    }

    if (options.allowedTypes && !this.isValidType(file, options.allowedTypes)) {
      throw new Error(`File type not allowed. Allowed types: ${options.allowedTypes.join(', ')}`);
    }

    if (options.allowedExtensions && !this.isValidExtension(file, options.allowedExtensions)) {
      throw new Error(`File extension not allowed. Allowed extensions: ${options.allowedExtensions.join(', ')}`);
    }
  }
}

// 6. Batch File Operations
class BatchFileOperations {
  private reader = new TextFileReader();
  private fileInfo = new FileInfoHelper();

  // Read multiple files
  async readMultipleFiles(files: File[]): Promise<Map<File, string>> {
    const results = new Map<File, string>();

    for (const file of files) {
      try {
        const content = await this.reader.readAsText(file);
        results.set(file, content);
      } catch (error) {
        console.error(`Failed to read ${file.name}:`, error);
      }
    }

    return results;
  }

  // Get total size of files
  getTotalSize(files: File[]): number {
    return files.reduce((total, file) => total + file.size, 0);
  }

  // Filter files by type
  filterByType(files: File[], mimeType: string): File[] {
    return files.filter(file => file.type === mimeType);
  }

  // Filter files by extension
  filterByExtension(files: File[], extension: string): File[] {
    return files.filter(file => file.name.endsWith(extension));
  }

  // Sort files by name
  sortByName(files: File[], ascending: boolean = true): File[] {
    return [...files].sort((a, b) => {
      return ascending
        ? a.name.localeCompare(b.name)
        : b.name.localeCompare(a.name);
    });
  }

  // Sort files by size
  sortBySize(files: File[], ascending: boolean = true): File[] {
    return [...files].sort((a, b) => {
      return ascending ? a.size - b.size : b.size - a.size;
    });
  }

  // Group files by type
  groupByType(files: File[]): Map<string, File[]> {
    const groups = new Map<string, File[]>();

    for (const file of files) {
      const type = file.type || 'unknown';
      if (!groups.has(type)) {
        groups.set(type, []);
      }
      groups.get(type)!.push(file);
    }

    return groups;
  }
}

// 7. File Processing Utilities
class FileProcessingUtils {
  // Count words in text
  countWords(text: string): number {
    const words = text.trim().split(/\s+/);
    return words.filter(word => word.length > 0).length;
  }

  // Count lines in text
  countLines(text: string): number {
    return text.split('\n').length;
  }

  // Count characters in text
  countCharacters(text: string, includeSpaces: boolean = true): number {
    if (includeSpaces) {
      return text.length;
    }
    return text.replace(/\s/g, '').length;
  }

  // Search for text in file
  async searchInFile(file: File, searchText: string): Promise<number[]> {
    const reader = new TextFileReader();
    const content = await reader.readAsText(file);
    const lines = content.split('\n');
    const matches: number[] = [];

    for (let i = 0; i < lines.length; i++) {
      if (lines[i].toLowerCase().includes(searchText.toLowerCase())) {
        matches.push(i + 1);
      }
    }

    return matches;
  }

  // Replace text in file
  async replaceInFile(file: File, searchText: string, replaceWith: string): Promise<string> {
    const reader = new TextFileReader();
    const content = await reader.readAsText(file);
    return content.replaceAll(searchText, replaceWith);
  }

  // Convert file to JSON (if CSV or similar)
  async parseCSV(file: File): Promise<string[][]> {
    const reader = new TextFileReader();
    const content = await reader.readAsText(file);
    const lines = content.split('\n');

    return lines.map(line => {
      const values: string[] = [];
      let current = '';
      let inQuotes = false;

      for (let i = 0; i < line.length; i++) {
        const char = line[i];

        if (char === '"') {
          inQuotes = !inQuotes;
        } else if (char === ',' && !inQuotes) {
          values.push(current.trim());
          current = '';
        } else {
          current += char;
        }
      }

      values.push(current.trim());
      return values;
    });
  }
}

// 8. File Download Helper
class FileDownloadHelper {
  // Download text as file
  downloadText(fileName: string, text: string): void {
    const blob = new Blob([text], { type: 'text/plain' });
    this.downloadBlob(blob, fileName);
  }

  // Download JSON as file
  downloadJSON(fileName: string, data: any): void {
    const json = JSON.stringify(data, null, 2);
    const blob = new Blob([json], { type: 'application/json' });
    this.downloadBlob(blob, fileName);
  }

  // Download data URL as file
  downloadDataURL(fileName: string, dataURL: string): void {
    const link = document.createElement('a');
    link.href = dataURL;
    link.download = fileName;
    document.body.appendChild(link);
    link.click();
    document.body.removeChild(link);
  }

  // Download blob
  downloadBlob(blob: Blob, fileName: string): void {
    const url = URL.createObjectURL(blob);
    const link = document.createElement('a');
    link.href = url;
    link.download = fileName;
    document.body.appendChild(link);
    link.click();
    document.body.removeChild(link);
    URL.revokeObjectURL(url);
  }
}

// Usage Examples
async function demonstrateTextFileOperations() {
  console.log('=== Web TypeScript Text File Read/Write Examples ===\n');

  const fileSelector = new FileSelector();
  const reader = new TextFileReader();
  const writer = new TextFileWriter();
  const fileInfo = new FileInfoHelper();
  const validator = new FileValidator();
  const batchOps = new BatchFileOperations();
  const processor = new FileProcessingUtils();
  const downloader = new FileDownloadHelper();

  // 1. Select and read file
  console.log('--- 1. Select and Read File ---');
  try {
    const file = await fileSelector.selectFile('.txt');
    fileInfo.printFileInfo(file);

    const content = await reader.readAsText(file);
    console.log(`File content: ${content.substring(0, 100)}...`);
  } catch (error) {
    console.error(`Error: ${(error as Error).message}`);
  }

  // 2. Write text file
  console.log('\n--- 2. Write Text File ---');
  writer.writeText('example.txt', 'Hello, World!\nThis is a test file.');
  console.log('File written and downloaded');

  // 3. Write multiple lines
  console.log('\n--- 3. Write Lines ---');
  const lines = ['Line 1', 'Line 2', 'Line 3', 'Line 4', 'Line 5'];
  writer.writeLines('lines.txt', lines);
  console.log('Lines file written and downloaded');

  // 4. Read with encoding
  console.log('\n--- 4. Read with Encoding ---');
  try {
    const file = await fileSelector.selectFile('.txt');
    const contentUTF8 = await reader.readAsTextWithEncoding(file, 'UTF-8');
    console.log(`UTF-8 content: ${contentUTF8.substring(0, 100)}...`);
  } catch (error) {
    console.error(`Error: ${(error as Error).message}`);
  }

  // 5. Read as data URL
  console.log('\n--- 5. Read as Data URL ---');
  try {
    const file = await fileSelector.selectFile('.txt');
    const dataURL = await reader.readAsDataURL(file);
    console.log(`Data URL: ${dataURL.substring(0, 100)}...`);
  } catch (error) {
    console.error(`Error: ${(error as Error).message}`);
  }

  // 6. File validation
  console.log('\n--- 6. File Validation ---');
  try {
    const file = await fileSelector.selectFile('*');
    console.log(`Is text file: ${validator.isTextFile(file)}`);
    console.log(`Valid size (< 10MB): ${validator.isValidSize(file, 10 * 1024 * 1024)}`);

    validator.validate(file, {
      maxSize: 10 * 1024 * 1024,
      allowedExtensions: ['txt', 'md', 'json']
    });
    console.log('File validation passed');
  } catch (error) {
    console.error(`Validation error: ${(error as Error).message}`);
  }

  // 7. Batch operations
  console.log('\n--- 7. Batch Operations ---');
  try {
    const files = await fileSelector.selectMultipleFiles('*');
    console.log(`Total files: ${files.length}`);
    console.log(`Total size: ${fileInfo.getReadableSize(batchOps.getTotalSize(files))}`);

    const textFiles = batchOps.filterByType(files, 'text/plain');
    console.log(`Text files: ${textFiles.length}`);

    const sortedFiles = batchOps.sortByName(files);
    console.log('Files sorted by name');

    const groupedFiles = batchOps.groupByType(files);
    console.log(`File types: ${groupedFiles.size}`);
  } catch (error) {
    console.error(`Error: ${(error as Error).message}`);
  }

  // 8. File processing
  console.log('\n--- 8. File Processing ---');
  try {
    const file = await fileSelector.selectFile('.txt');
    const content = await reader.readAsText(file);

    console.log(`Word count: ${processor.countWords(content)}`);
    console.log(`Line count: ${processor.countLines(content)}`);
    console.log(`Character count: ${processor.countCharacters(content)}`);

    const matches = await processor.searchInFile(file, 'test');
    console.log(`Search matches on lines: ${matches.join(', ')}`);
  } catch (error) {
    console.error(`Error: ${(error as Error).message}`);
  }

  // 9. Download helpers
  console.log('\n--- 9. Download Helpers ---');
  downloader.downloadText('download.txt', 'Downloaded content');
  downloader.downloadJSON('data.json', { name: 'Test', value: 123 });

  console.log('\n=== All Text File Operations Completed ===');
}

// Export functions
export { TextFileReader, TextFileWriter, FileInfoHelper, FileSelector, FileValidator, BatchFileOperations, FileProcessingUtils, FileDownloadHelper };
export { demonstrateTextFileOperations };

💻 Copiar/Mover Archivos typescript

🟡 intermediate ⭐⭐⭐

Copiar y mover archivos con seguimiento de progreso y manejo de errores en el navegador

⏱️ 25 min 🏷️ typescript, web, file operations
Prerequisites: Intermediate TypeScript, File API
// Web TypeScript File Copy/Move Examples
// Browser-based file operations using File API and Blob operations

// 1. File Copy Operations
class FileCopier {
  // Copy file (creates a new blob with same content)
  copy(file: File): Blob {
    return new Blob([file], { type: file.type });
  }

  // Copy file with new name
  copyAs(file: File, newName: string): File {
    const blob = this.copy(file);
    return new File([blob], newName, { type: file.type });
  }

  // Copy multiple files
  copyMultiple(files: File[]): File[] {
    return files.map(file => this.copyAs(file, file.name));
  }

  // Copy with metadata
  copyWithMetadata(file: File, metadata: Record<string, any>): File {
    const blob = this.copy(file);
    const newName = metadata.newName || file.name;

    const newFile = new File([blob], newName, {
      type: file.type,
      lastModified: metadata.lastModified || file.lastModified
    });

    return newFile;
  }

  // Clone file array
  cloneFileList(files: FileList | File[]): File[] {
    return Array.from(files).map(file => this.copyAs(file, file.name));
  }
}

// 2. File Merge Operations
class FileMerger {
  // Merge text files
  async mergeTextFiles(files: File[], separator: string = '\n\n'): Promise<string> {
    const reader = new TextFileReader();
    const contents: string[] = [];

    for (const file of files) {
      const content = await reader.readAsText(file);
      contents.push(content);
    }

    return contents.join(separator);
  }

  // Merge files as blob
  async mergeAsBlob(files: File[], mimeType: string): Promise<Blob> {
    const parts: BlobPart[] = [];

    for (const file of files) {
      const arrayBuffer = await file.arrayBuffer();
      parts.push(arrayBuffer);
    }

    return new Blob(parts, { type: mimeType });
  }

  // Merge CSV files
  async mergeCSVFiles(files: File[], skipHeaders: boolean = false): Promise<string> {
    const reader = new TextFileReader();
    let merged = '';

    for (let i = 0; i < files.length; i++) {
      const content = await reader.readAsText(files[i]);
      const lines = content.split('\n');

      let startLine = 0;
      if (skipHeaders && i > 0) {
        startLine = 1; // Skip header row
      }

      const relevantLines = lines.slice(startLine).join('\n');
      merged += (i > 0 ? '\n' : '') + relevantLines;
    }

    return merged;
  }

  // Append file to another
  async appendFile(baseFile: File, fileToAppend: File): Promise<File> {
    const reader = new TextFileReader();
    const baseContent = await reader.readAsText(baseFile);
    const appendContent = await reader.readAsText(fileToAppend);

    const merged = baseContent + '\n' + appendContent;
    const blob = new Blob([merged], { type: baseFile.type });

    return new File([blob], baseFile.name, { type: baseFile.type });
  }
}

// 3. Batch File Operations
class BatchFileOperations {
  // Batch rename files
  batchRename(files: File[], newNamePattern: string, startIndex: number = 1): File[] {
    const renamedFiles: File[] = [];

    files.forEach((file, index) => {
      const extension = file.name.split('.').pop();
      const newName = newNamePattern.replace('{n}', String(startIndex + index));
      const fullNewName = extension ? `${newName}.${extension}` : newName;

      const blob = new Blob([file], { type: file.type });
      const newFile = new File([blob], fullNewName, { type: file.type });

      renamedFiles.push(newFile);
    });

    return renamedFiles;
  }

  // Batch change extension
  batchChangeExtension(files: File[], newExtension: string): File[] {
    return files.map(file => {
      const nameWithoutExt = file.name.replace(/\.[^.]*$/, '');
      const newName = `${nameWithoutExt}.${newExtension}`;

      const blob = new Blob([file], { type: file.type });
      return new File([blob], newName, { type: file.type });
    });
  }

  // Add prefix to filenames
  batchAddPrefix(files: File[], prefix: string): File[] {
    return files.map(file => {
      const newName = prefix + file.name;
      const blob = new Blob([file], { type: file.type });
      return new File([blob], newName, { type: file.type });
    });
  }

  // Add suffix to filenames
  batchAddSuffix(files: File[], suffix: string): File[] {
    return files.map(file => {
      const nameParts = file.name.split('.');
      const name = nameParts.slice(0, -1).join('.');
      const ext = nameParts.length > 1 ? nameParts[nameParts.length - 1] : '';
      const newName = ext ? `${name}${suffix}.${ext}` : `${name}${suffix}`;

      const blob = new Blob([file], { type: file.type });
      return new File([blob], newName, { type: file.type });
    });
  }
}

// 4. File Transform Operations
class FileTransformer {
  // Convert file to base64
  async toBase64(file: File): Promise<string> {
    const arrayBuffer = await file.arrayBuffer();
    const bytes = new Uint8Array(arrayBuffer);
    let binary = '';

    for (const byte of bytes) {
      binary += String.fromCharCode(byte);
    }

    return btoa(binary);
  }

  // Convert base64 to file
  base64ToFile(base64: string, fileName: string, mimeType: string): File {
    const binary = atob(base64);
    const bytes = new Uint8Array(binary.length);

    for (let i = 0; i < binary.length; i++) {
      bytes[i] = binary.charCodeAt(i);
    }

    const blob = new Blob([bytes], { type: mimeType });
    return new File([blob], fileName, { type: mimeType });
  }

  // Convert text file encoding
  async convertEncoding(file: File, fromEncoding: string, toEncoding: string): Promise<File> {
    const reader = new TextFileReader();
    const text = await reader.readAsTextWithEncoding(file, fromEncoding);

    const encoder = new TextEncoder();
    const uint8Array = encoder.encode(text);
    const blob = new Blob([uint8Array], { type: `text/plain;charset=${toEncoding}` });

    return new File([blob], file.name, { type: blob.type });
  }

  // Compress file info (reduce metadata)
  stripMetadata(file: File): File {
    const blob = new Blob([file], { type: file.type });
    return new File([blob], file.name, { type: file.type });
  }
}

// 5. File Comparison
class FileComparer {
  // Compare file sizes
  compareBySize(file1: File, file2: File): number {
    return file1.size - file2.size;
  }

  // Compare by name
  compareByName(file1: File, file2: File): number {
    return file1.name.localeCompare(file2.name);
  }

  // Compare by type
  compareByType(file1: File, file2: File): number {
    return file1.type.localeCompare(file2.type);
  }

  // Compare by date
  compareByDate(file1: File, file2: File): number {
    return file1.lastModified - file2.lastModified;
  }

  // Check if files are identical (by size and content)
  async areIdentical(file1: File, file2: File): Promise<boolean> {
    if (file1.size !== file2.size) {
      return false;
    }

    const buffer1 = await file1.arrayBuffer();
    const buffer2 = await file2.arrayBuffer();

    const bytes1 = new Uint8Array(buffer1);
    const bytes2 = new Uint8Array(buffer2);

    for (let i = 0; i < bytes1.length; i++) {
      if (bytes1[i] !== bytes2[i]) {
        return false;
      }
    }

    return true;
  }

  // Find duplicate files
  async findDuplicates(files: File[]): Promise<Map<string, File[]>> {
    const duplicates = new Map<string, File[]>();
    const signatures = new Map<string, File[]>();

    for (const file of files) {
      // Use size and first 1KB as quick signature
      const chunk = file.slice(0, 1024);
      const arrayBuffer = await chunk.arrayBuffer();
      const bytes = new Uint8Array(arrayBuffer);
      const signature = `${file.size}-${Array.from(bytes.slice(0, 32)).join('-')}`;

      if (!signatures.has(signature)) {
        signatures.set(signature, []);
      }
      signatures.get(signature)!.push(file);
    }

    // Check potential duplicates with full comparison
    for (const [signature, filesList] of signatures) {
      if (filesList.length > 1) {
        const confirmedDupes: File[] = [];

        for (let i = 0; i < filesList.length; i++) {
          for (let j = i + 1; j < filesList.length; j++) {
            if (await this.areIdentical(filesList[i], filesList[j])) {
              if (!confirmedDupes.includes(filesList[i])) {
                confirmedDupes.push(filesList[i]);
              }
              if (!confirmedDupes.includes(filesList[j])) {
                confirmedDupes.push(filesList[j]);
              }
            }
          }
        }

        if (confirmedDupes.length > 1) {
          duplicates.set(confirmedDupes[0].name, confirmedDupes);
        }
      }
    }

    return duplicates;
  }
}

// 6. File Organization
class FileOrganizer {
  // Organize files by type
  organizeByType(files: File[]): Map<string, File[]> {
    const organized = new Map<string, File[]>();

    for (const file of files) {
      const type = file.type.split('/')[0] || 'unknown';

      if (!organized.has(type)) {
        organized.set(type, []);
      }

      organized.get(type)!.push(file);
    }

    return organized;
  }

  // Organize by extension
  organizeByExtension(files: File[]): Map<string, File[]> {
    const organized = new Map<string, File[]>();

    for (const file of files) {
      const ext = file.name.split('.').pop()?.toLowerCase() || 'no-extension';

      if (!organized.has(ext)) {
        organized.set(ext, []);
      }

      organized.get(ext)!.push(file);
    }

    return organized;
  }

  // Organize by size range
  organizeBySize(files: File[]): Map<string, File[]> {
    const organized = new Map<string, File[]>();

    const sizeRanges = [
      { name: 'Small (< 100KB)', max: 100 * 1024 },
      { name: 'Medium (100KB - 1MB)', min: 100 * 1024, max: 1024 * 1024 },
      { name: 'Large (1MB - 10MB)', min: 1024 * 1024, max: 10 * 1024 * 1024 },
      { name: 'Very Large (> 10MB)', min: 10 * 1024 * 1024 }
    ];

    for (const file of files) {
      for (const range of sizeRanges) {
        if ((!range.min || file.size >= range.min) &&
            (!range.max || file.size < range.max)) {
          if (!organized.has(range.name)) {
            organized.set(range.name, []);
          }
          organized.get(range.name)!.push(file);
          break;
        }
      }
    }

    return organized;
  }

  // Organize by date
  organizeByDate(files: File[]): Map<string, File[]> {
    const organized = new Map<string, File[]>();

    for (const file of files) {
      const date = new Date(file.lastModified);
      const dateKey = `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}`;

      if (!organized.has(dateKey)) {
        organized.set(dateKey, []);
      }

      organized.get(dateKey)!.push(file);
    }

    return organized;
  }
}

// 7. File Operations with Progress
class FileOperationsWithProgress {
  // Copy with progress callback
  async copyWithProgress(file: File, onProgress: (progress: number) => void): Promise<Blob> {
    const chunkSize = 1024 * 1024; // 1MB chunks
    const chunks: BlobPart[] = [];
    let offset = 0;

    while (offset < file.size) {
      const end = Math.min(offset + chunkSize, file.size);
      const chunk = file.slice(offset, end);
      const arrayBuffer = await chunk.arrayBuffer();
      chunks.push(arrayBuffer);

      offset = end;
      const progress = Math.floor((offset / file.size) * 100);
      onProgress(progress);
    }

    return new Blob(chunks, { type: file.type });
  }

  // Batch copy with progress
  async batchCopyWithProgress(
    files: File[],
    onProgress: (current: number, total: number, file: File) => void
  ): Promise<File[]> {
    const copiedFiles: File[] = [];

    for (let i = 0; i < files.length; i++) {
      const copied = await this.copyWithProgress(files[i], () => {});
      const newFile = new File([copied], files[i].name, { type: files[i].type });
      copiedFiles.push(newFile);

      onProgress(i + 1, files.length, files[i]);
    }

    return copiedFiles;
  }
}

// Import helper classes
class TextFileReader {
  async readAsText(file: File): Promise<string> {
    return new Promise((resolve, reject) => {
      const reader = new FileReader();
      reader.onload = () => resolve(reader.result as string);
      reader.onerror = () => reject(new Error('Failed to read file'));
      reader.readAsText(file);
    });
  }

  async readAsTextWithEncoding(file: File, encoding: string): Promise<string> {
    return new Promise((resolve, reject) => {
      const reader = new FileReader();
      reader.onload = () => resolve(reader.result as string);
      reader.onerror = () => reject(new Error('Failed to read file'));
      reader.readAsText(file, encoding);
    });
  }
}

// Usage Examples
async function demonstrateFileCopyMove() {
  console.log('=== Web TypeScript File Copy/Move Examples ===\n');

  const fileSelector = new FileSelector();
  const copier = new FileCopier();
  const merger = new FileMerger();
  const batchOps = new BatchFileOperations();
  const transformer = new FileTransformer();
  const comparer = new FileComparer();
  const organizer = new FileOrganizer();
  const progressOps = new FileOperationsWithProgress();

  // 1. Copy file
  console.log('--- 1. Copy File ---');
  try {
    const file = await fileSelector.selectFile('*');
    const copied = copier.copy(file);
    console.log(`Copied file size: ${copied.size}`);

    const copyAs = copier.copyAs(file, 'copy_' + file.name);
    console.log(`Copied as: ${copyAs.name}`);
  } catch (error) {
    console.error(`Error: ${(error as Error).message}`);
  }

  // 2. Merge files
  console.log('\n--- 2. Merge Files ---');
  try {
    const files = await fileSelector.selectMultipleFiles('.txt');
    const merged = await merger.mergeTextFiles(files);
    console.log(`Merged content length: ${merged.length}`);

    // Download merged file
    const blob = new Blob([merged], { type: 'text/plain' });
    const url = URL.createObjectURL(blob);
    const link = document.createElement('a');
    link.href = url;
    link.download = 'merged.txt';
    link.click();
    URL.revokeObjectURL(url);
  } catch (error) {
    console.error(`Error: ${(error as Error).message}`);
  }

  // 3. Batch rename
  console.log('\n--- 3. Batch Rename ---');
  try {
    const files = await fileSelector.selectMultipleFiles('*');
    const renamed = batchOps.batchRename(files, 'file_{n}');
    console.log('Renamed files:');
    renamed.forEach(file => console.log(`  - ${file.name}`));
  } catch (error) {
    console.error(`Error: ${(error as Error).message}`);
  }

  // 4. Convert to base64
  console.log('\n--- 4. Convert to Base64 ---');
  try {
    const file = await fileSelector.selectFile('image/*');
    const base64 = await transformer.toBase64(file);
    console.log(`Base64 length: ${base64.length}`);
    console.log(`Base64 preview: ${base64.substring(0, 100)}...`);
  } catch (error) {
    console.error(`Error: ${(error as Error).message}`);
  }

  // 5. Find duplicates
  console.log('\n--- 5. Find Duplicates ---');
  try {
    const files = await fileSelector.selectMultipleFiles('*');
    const duplicates = await comparer.findDuplicates(files);

    console.log(`Found ${duplicates.size} sets of duplicates`);
    for (const [name, dupes] of duplicates) {
      console.log(`  - ${name}: ${dupes.length} copies`);
    }
  } catch (error) {
    console.error(`Error: ${(error as Error).message}`);
  }

  // 6. Organize by type
  console.log('\n--- 6. Organize by Type ---');
  try {
    const files = await fileSelector.selectMultipleFiles('*');
    const organized = organizer.organizeByType(files);

    for (const [type, filesList] of organized) {
      console.log(`${type}: ${filesList.length} files`);
    }
  } catch (error) {
    console.error(`Error: ${(error as Error).message}`);
  }

  // 7. Copy with progress
  console.log('\n--- 7. Copy with Progress ---');
  try {
    const file = await fileSelector.selectFile('*');

    const copied = await progressOps.copyWithProgress(file, (progress) => {
      console.log(`Progress: ${progress}%`);
    });

    console.log(`Copy completed. Size: ${copied.size}`);
  } catch (error) {
    console.error(`Error: ${(error as Error).message}`);
  }

  console.log('\n=== All File Copy/Move Examples Completed ===');
}

class FileSelector {
  async selectFile(accept: string): Promise<File> {
    return new Promise((resolve, reject) => {
      const input = document.createElement('input');
      input.type = 'file';
      input.accept = accept;

      input.onchange = (e) => {
        const files = (e.target as HTMLInputElement).files;
        if (files && files.length > 0) {
          resolve(files[0]);
        } else {
          reject(new Error('No file selected'));
        }
      };

      input.click();
    });
  }

  async selectMultipleFiles(accept: string): Promise<File[]> {
    return new Promise((resolve, reject) => {
      const input = document.createElement('input');
      input.type = 'file';
      input.accept = accept;
      input.multiple = true;

      input.onchange = (e) => {
        const files = (e.target as HTMLInputElement).files;
        if (files && files.length > 0) {
          resolve(Array.from(files));
        } else {
          reject(new Error('No files selected'));
        }
      };

      input.click();
    });
  }
}

// Export functions
export { FileCopier, FileMerger, BatchFileOperations, FileTransformer, FileComparer, FileOrganizer, FileOperationsWithProgress };
export { demonstrateFileCopyMove };

💻 Recorrido de Directorio typescript

🔴 complex ⭐⭐⭐⭐

Atravesar directorios usando webkitdirectory y FileSystem API

⏱️ 30 min 🏷️ typescript, web, file operations, directory
Prerequisites: Advanced TypeScript, FileSystem API
// Web TypeScript Directory Traversal Examples
// Using webkitDirectory, FileSystemDirectoryHandle, and FileSystem API

// 1. Directory Selection
class DirectorySelector {
  // Select directory using webkitdirectory
  async selectDirectory(): Promise<File[]> {
    return new Promise((resolve, reject) => {
      const input = document.createElement('input');
      input.type = 'file';
      input.webkitdirectory = true;
      input.multiple = true;

      input.onchange = (e) => {
        const files = (e.target as HTMLInputElement).files;
        if (files && files.length > 0) {
          resolve(Array.from(files));
        } else {
          reject(new Error('No files selected'));
        }
      };

      input.oncancel = () => {
        reject(new Error('Directory selection cancelled'));
      };

      input.click();
    });
  }

  // Select directory with FileSystem API (modern browsers)
  async selectDirectoryHandle(): Promise<FileSystemDirectoryHandle> {
    if ('showDirectoryPicker' in window) {
      const handle = await (window as any).showDirectoryPicker();
      return handle;
    } else {
      throw new Error('FileSystem API not supported');
    }
  }
}

// 2. Directory Traversal
class DirectoryTraverser {
  // Build directory tree from files
  buildDirectoryTree(files: File[]): DirectoryNode {
    const root = new DirectoryNode('root', '/', true);

    for (const file of files) {
      const pathParts = file.webkitRelativePath.split('/');
      let currentNode = root;

      for (let i = 0; i < pathParts.length - 1; i++) {
        const part = pathParts[i];
        let child = currentNode.children.find(c => c.name === part);

        if (!child) {
          child = new DirectoryNode(part, pathParts.slice(0, i + 1).join('/'), true);
          currentNode.children.push(child);
        }

        currentNode = child;
      }

      // Add file node
      const fileName = pathParts[pathParts.length - 1];
      currentNode.children.push(new DirectoryNode(fileName, file.webkitRelativePath, false, file));
    }

    return root;
  }

  // Flatten directory tree
  flattenDirectoryTree(node: DirectoryNode, path: string = ''): File[] {
    const files: File[] = [];

    for (const child of node.children) {
      const childPath = path ? `${path}/${child.name}` : child.name;

      if (child.isDirectory) {
        files.push(...this.flattenDirectoryTree(child, childPath));
      } else if (child.file) {
        files.push(child.file);
      }
    }

    return files;
  }

  // Print directory tree
  printDirectoryTree(node: DirectoryNode, indent: number = 0): void {
    const prefix = '  '.repeat(indent);
    const icon = node.isDirectory ? '📁' : '📄';
    console.log(`${icon} ${prefix}${node.name}`);

    for (const child of node.children) {
      this.printDirectoryTree(child, indent + 1);
    }
  }

  // Get directory statistics
  getDirectoryStats(node: DirectoryNode): DirectoryStats {
    const stats = new DirectoryStats();

    this.calculateStats(node, stats);

    return stats;
  }

  private calculateStats(node: DirectoryNode, stats: DirectoryStats): void {
    if (node.isDirectory) {
      stats.directoryCount++;

      for (const child of node.children) {
        this.calculateStats(child, stats);
      }
    } else {
      stats.fileCount++;
      stats.totalSize += node.file?.size || 0;
    }
  }

  // Find files by name
  findByName(node: DirectoryNode, name: string, recursive: boolean = true): DirectoryNode[] {
    const results: DirectoryNode[] = [];

    this.searchByName(node, name, results, recursive);

    return results;
  }

  private searchByName(node: DirectoryNode, name: string, results: DirectoryNode[], recursive: boolean): void {
    for (const child of node.children) {
      if (child.name.toLowerCase().includes(name.toLowerCase())) {
        results.push(child);
      }

      if (recursive && child.isDirectory) {
        this.searchByName(child, name, results, recursive);
      }
    }
  }

  // Find files by extension
  findByExtension(node: DirectoryNode, extensions: string[]): DirectoryNode[] {
    const results: DirectoryNode[] = [];

    this.searchByExtension(node, extensions, results);

    return results;
  }

  private searchByExtension(node: DirectoryNode, extensions: string[], results: DirectoryNode[]): void {
    for (const child of node.children) {
      if (!child.isDirectory && child.file) {
        const ext = child.name.split('.').pop()?.toLowerCase();

        if (ext && extensions.includes(ext)) {
          results.push(child);
        }
      }

      if (child.isDirectory) {
        this.searchByExtension(child, extensions, results);
      }
    }
  }

  // Filter files by size
  filterBySize(node: DirectoryNode, minSize: number, maxSize: number): DirectoryNode[] {
    const results: DirectoryNode[] = [];

    this.searchBySize(node, minSize, maxSize, results);

    return results;
  }

  private searchBySize(node: DirectoryNode, minSize: number, maxSize: number, results: DirectoryNode[]): void {
    for (const child of node.children) {
      if (!child.isDirectory && child.file) {
        const size = child.file.size;

        if (size >= minSize && size <= maxSize) {
          results.push(child);
        }
      }

      if (child.isDirectory) {
        this.searchBySize(child, minSize, maxSize, results);
      }
    }
  }
}

// 3. File System API Operations (Modern Browsers)
class FileSystemOperations {
  // Read directory contents using FileSystem API
  async readDirectory(handle: FileSystemDirectoryHandle): Promise<Map<string, FileSystemHandle>> {
    const entries = new Map<string, FileSystemHandle>();

    for await (const entry of handle.values()) {
      entries.set(entry.name, entry);
    }

    return entries;
  }

  // Recursively read directory
  async readDirectoryRecursively(handle: FileSystemDirectoryHandle, path: string = ''): Promise<Map<string, FileSystemHandle>> {
    const entries = new Map<string, FileSystemHandle>();

    for await (const entry of handle.values()) {
      const entryPath = path ? `${path}/${entry.name}` : entry.name;
      entries.set(entryPath, entry);

      if (entry.kind === 'directory') {
        const dirHandle = entry as FileSystemDirectoryHandle;
        const subEntries = await this.readDirectoryRecursively(dirHandle, entryPath);

        for (const [subPath, subEntry] of subEntries) {
          entries.set(subPath, subEntry);
        }
      }
    }

    return entries;
  }

  // Get file from directory
  async getFile(handle: FileSystemDirectoryHandle, fileName: string): Promise<File> {
    const fileHandle = await handle.getFileHandle(fileName);
    const file = await fileHandle.getFile();
    return file;
  }

  // Get all files from directory
  async getAllFiles(handle: FileSystemDirectoryHandle): Promise<File[]> {
    const files: File[] = [];

    for await (const entry of handle.values()) {
      if (entry.kind === 'file') {
        const fileHandle = entry as FileSystemFileHandle;
        const file = await fileHandle.getFile();
        files.push(file);
      } else if (entry.kind === 'directory') {
        const dirHandle = entry as FileSystemDirectoryHandle;
        const subFiles = await this.getAllFiles(dirHandle);
        files.push(...subFiles);
      }
    }

    return files;
  }

  // Create directory
  async createDirectory(handle: FileSystemDirectoryHandle, dirName: string): Promise<FileSystemDirectoryHandle> {
    return await handle.getDirectoryHandle(dirName, { create: true });
  }

  // Create file
  async createFile(handle: FileSystemDirectoryHandle, fileName: string, content: string): Promise<void> {
    const fileHandle = await handle.getFileHandle(fileName, { create: true });
    const writable = await fileHandle.createWritable();
    await writable.write(content);
    await writable.close();
  }

  // Delete file
  async deleteFile(handle: FileSystemDirectoryHandle, fileName: string): Promise<void> {
    await handle.removeEntry(fileName);
  }

  // Delete directory
  async deleteDirectory(handle: FileSystemDirectoryHandle, dirName: string): Promise<void> {
    await handle.removeEntry(dirName, { recursive: true });
  }
}

// 4. Directory Tree Structure
class DirectoryNode {
  name: string;
  path: string;
  isDirectory: boolean;
  file?: File;
  children: DirectoryNode[];

  constructor(name: string, path: string, isDirectory: boolean, file?: File) {
    this.name = name;
    this.path = path;
    this.isDirectory = isDirectory;
    this.file = file;
    this.children = [];
  }

  // Sort children by name
  sortByName(): void {
    this.children.sort((a, b) => a.name.localeCompare(b.name));

    for (const child of this.children) {
      if (child.isDirectory) {
        child.sortByName();
      }
    }
  }

  // Sort children by size (files only)
  sortBySize(): void {
    this.children.sort((a, b) => {
      if (a.isDirectory && !b.isDirectory) return -1;
      if (!a.isDirectory && b.isDirectory) return 1;

      if (!a.isDirectory && !b.isDirectory) {
        return (a.file?.size || 0) - (b.file?.size || 0);
      }

      return 0;
    });

    for (const child of this.children) {
      if (child.isDirectory) {
        child.sortBySize();
      }
    }
  }

  // Filter by predicate
  filter(predicate: (node: DirectoryNode) => boolean): DirectoryNode | null {
    if (!predicate(this)) {
      return null;
    }

    const filteredNode = new DirectoryNode(this.name, this.path, this.isDirectory, this.file);

    for (const child of this.children) {
      const filteredChild = child.filter(predicate);
      if (filteredChild) {
        filteredNode.children.push(filteredChild);
      }
    }

    return filteredNode;
  }
}

// 5. Directory Statistics
class DirectoryStats {
  fileCount: number = 0;
  directoryCount: number = 0;
  totalSize: number = 0;
  extensionCounts: Map<string, number> = new Map();

  // Get largest files
  getLargestFiles(node: DirectoryNode, count: number = 10): DirectoryNode[] {
    const files: DirectoryNode[] = [];

    this.collectFiles(node, files);

    return files
      .sort((a, b) => (b.file?.size || 0) - (a.file?.size || 0))
      .slice(0, count);
  }

  private collectFiles(node: DirectoryNode, files: DirectoryNode[]): void {
    for (const child of node.children) {
      if (child.isDirectory) {
        this.collectFiles(child, files);
      } else {
        files.push(child);
      }
    }
  }

  // Get extension statistics
  getExtensionStats(): Map<string, number> {
    return new Map([...this.extensionCounts.entries()].sort((a, b) => b[1] - a[1]));
  }

  // Print statistics
  print(): void {
    console.log('\n=== Directory Statistics ===');
    console.log(`Files: ${this.fileCount}`);
    console.log(`Directories: ${this.directoryCount}`);
    console.log(`Total Size: ${this.formatBytes(this.totalSize)}`);

    console.log('\nExtension Distribution:');
    const extStats = this.getExtensionStats();

    for (const [ext, count] of extStats) {
      console.log(`  .${ext}: ${count}`);
    }
  }

  private formatBytes(bytes: number): string {
    const kb = bytes / 1024;
    const mb = kb / 1024;
    const gb = mb / 1024;

    if (gb >= 1) {
      return `${gb.toFixed(2)} GB`;
    } else if (mb >= 1) {
      return `${mb.toFixed(2)} MB`;
    } else if (kb >= 1) {
      return `${kb.toFixed(2)} KB`;
    } else {
      return `${bytes} bytes`;
    }
  }
}

// 6. Advanced Directory Operations
class AdvancedDirectoryOperations {
  private traverser = new DirectoryTraverser();
  private fsOps = new FileSystemOperations();

  // Synchronize directories
  async syncDirectories(source: FileSystemDirectoryHandle, target: FileSystemDirectoryHandle): Promise<void> {
    const sourceEntries = await this.fsOps.readDirectoryRecursively(source);
    const targetEntries = await this.fsOps.readDirectoryRecursively(target);

    for (const [path, entry] of sourceEntries) {
      if (entry.kind === 'file') {
        const sourceFile = entry as FileSystemFileHandle;
        const file = await sourceFile.getFile();

        if (!targetEntries.has(path)) {
          // Create file in target
          const pathParts = path.split('/');
          const fileName = pathParts[pathParts.length - 1];
          let targetDir = target;

          // Navigate/create subdirectories
          for (let i = 0; i < pathParts.length - 1; i++) {
            targetDir = await this.fsOps.createDirectory(targetDir, pathParts[i]);
          }

          // Copy file content
          const content = await file.text();
          await this.fsOps.createFile(targetDir, fileName, content);
        }
      }
    }
  }

  // Backup directory
  async backupDirectory(handle: FileSystemDirectoryHandle): Promise<Blob> {
    const files = await this.fsOps.getAllFiles(handle);
    const parts: BlobPart[] = [];

    for (const file of files) {
      const arrayBuffer = await file.arrayBuffer();
      parts.push(arrayBuffer);
    }

    return new Blob(parts, { type: 'application/zip' });
  }

  // Search in directory
  async searchInDirectory(
    handle: FileSystemDirectoryHandle,
    searchText: string,
    fileExtensions: string[] = ['txt', 'md', 'json']
  ): Promise<Map<string, string[]>> {
    const results = new Map<string, string[]>();

    for await (const entry of handle.values()) {
      if (entry.kind === 'file') {
        const ext = entry.name.split('.').pop()?.toLowerCase();

        if (ext && fileExtensions.includes(ext)) {
          const fileHandle = entry as FileSystemFileHandle;
          const file = await fileHandle.getFile();
          const content = await file.text();

          if (content.toLowerCase().includes(searchText.toLowerCase())) {
            const lines = content.split('\n');
            const matches: string[] = [];

            lines.forEach((line, index) => {
              if (line.toLowerCase().includes(searchText.toLowerCase())) {
                matches.push(`Line ${index + 1}: ${line.trim()}`);
              }
            });

            results.set(file.name, matches);
          }
        }
      } else if (entry.kind === 'directory') {
        const dirHandle = entry as FileSystemDirectoryHandle;
        const subResults = await this.searchInDirectory(dirHandle, searchText, fileExtensions);

        for (const [fileName, matches] of subResults) {
          results.set(`${entry.name}/${fileName}`, matches);
        }
      }
    }

    return results;
  }
}

// Usage Examples
async function demonstrateDirectoryTraversal() {
  console.log('=== Web TypeScript Directory Traversal Examples ===\n');

  const dirSelector = new DirectorySelector();
  const traverser = new DirectoryTraverser();
  const fsOps = new FileSystemOperations();
  const advancedOps = new AdvancedDirectoryOperations();

  // 1. Select directory (webkitdirectory)
  console.log('--- 1. Select Directory (webkitdirectory) ---');
  try {
    const files = await dirSelector.selectDirectory();
    console.log(`Selected ${files.length} files`);

    // Build directory tree
    const tree = traverser.buildDirectoryTree(files);
    traverser.printDirectoryTree(tree);

    // Get statistics
    const stats = traverser.getDirectoryStats(tree);
    stats.print();
  } catch (error) {
    console.error(`Error: ${(error as Error).message}`);
  }

  // 2. Select directory with FileSystem API
  console.log('\n--- 2. Select Directory (FileSystem API) ---');
  try {
    const handle = await dirSelector.selectDirectoryHandle();
    console.log(`Selected directory handle`);

    const entries = await fsOps.readDirectory(handle);
    console.log(`Directory contents: ${entries.size} entries`);

    for (const [name, entry] of entries) {
      const icon = entry.kind === 'directory' ? '📁' : '📄';
      console.log(`  ${icon} ${name}`);
    }
  } catch (error) {
    console.error(`Error: ${(error as Error).message}`);
  }

  // 3. Recursive directory read
  console.log('\n--- 3. Recursive Directory Read ---');
  try {
    const handle = await dirSelector.selectDirectoryHandle();
    const allEntries = await fsOps.readDirectoryRecursively(handle);

    console.log(`Total entries (recursive): ${allEntries.size}`);

    // Print first 20 entries
    let count = 0;
    for (const [path, entry] of allEntries) {
      if (count++ < 20) {
        const icon = entry.kind === 'directory' ? '📁' : '📄';
        console.log(`  ${icon} ${path}`);
      }
    }
  } catch (error) {
    console.error(`Error: ${(error as Error).message}`);
  }

  // 4. Get all files
  console.log('\n--- 4. Get All Files ---');
  try {
    const handle = await dirSelector.selectDirectoryHandle();
    const files = await fsOps.getAllFiles(handle);

    console.log(`Total files: ${files.length}`);
    console.log(`Total size: ${files.reduce((sum, f) => sum + f.size, 0)} bytes`);
  } catch (error) {
    console.error(`Error: ${(error as Error).message}`);
  }

  // 5. Search in directory
  console.log('\n--- 5. Search in Directory ---');
  try {
    const handle = await dirSelector.selectDirectoryHandle();
    const results = await advancedOps.searchInDirectory(handle, 'function');

    console.log(`Search results in ${results.size} files`);
    for (const [fileName, matches] of results) {
      console.log(`  ${fileName}: ${matches.length} matches`);
      matches.slice(0, 3).forEach(match => {
        console.log(`    ${match}`);
      });
    }
  } catch (error) {
    console.error(`Error: ${(error as Error).message}`);
  }

  console.log('\n=== All Directory Traversal Examples Completed ===');
}

// Export functions
export { DirectorySelector, DirectoryTraverser, FileSystemOperations, DirectoryNode, DirectoryStats, AdvancedDirectoryOperations };
export { demonstrateDirectoryTraversal };