/** * Async filesystem utilities for SmartProxy * Provides non-blocking alternatives to synchronous filesystem operations */ import * as plugins from '../../plugins.js'; export class AsyncFileSystem { /** * Check if a file or directory exists * @param path - Path to check * @returns Promise resolving to true if exists, false otherwise */ static async exists(path: string): Promise { try { await plugins.fs.promises.access(path); return true; } catch { return false; } } /** * Ensure a directory exists, creating it if necessary * @param dirPath - Directory path to ensure * @returns Promise that resolves when directory is ensured */ static async ensureDir(dirPath: string): Promise { await plugins.fs.promises.mkdir(dirPath, { recursive: true }); } /** * Read a file as string * @param filePath - Path to the file * @param encoding - File encoding (default: utf8) * @returns Promise resolving to file contents */ static async readFile(filePath: string, encoding: BufferEncoding = 'utf8'): Promise { return plugins.fs.promises.readFile(filePath, encoding); } /** * Read a file as buffer * @param filePath - Path to the file * @returns Promise resolving to file buffer */ static async readFileBuffer(filePath: string): Promise { return plugins.fs.promises.readFile(filePath); } /** * Write string data to a file * @param filePath - Path to the file * @param data - String data to write * @param encoding - File encoding (default: utf8) * @returns Promise that resolves when file is written */ static async writeFile(filePath: string, data: string, encoding: BufferEncoding = 'utf8'): Promise { // Ensure directory exists const dir = plugins.path.dirname(filePath); await this.ensureDir(dir); await plugins.fs.promises.writeFile(filePath, data, encoding); } /** * Write buffer data to a file * @param filePath - Path to the file * @param data - Buffer data to write * @returns Promise that resolves when file is written */ static async writeFileBuffer(filePath: string, data: Buffer): Promise { const dir = plugins.path.dirname(filePath); await this.ensureDir(dir); await plugins.fs.promises.writeFile(filePath, data); } /** * Remove a file * @param filePath - Path to the file * @returns Promise that resolves when file is removed */ static async remove(filePath: string): Promise { try { await plugins.fs.promises.unlink(filePath); } catch (error: any) { if (error.code !== 'ENOENT') { throw error; } // File doesn't exist, which is fine } } /** * Remove a directory and all its contents * @param dirPath - Path to the directory * @returns Promise that resolves when directory is removed */ static async removeDir(dirPath: string): Promise { try { await plugins.fs.promises.rm(dirPath, { recursive: true, force: true }); } catch (error: any) { if (error.code !== 'ENOENT') { throw error; } } } /** * Read JSON from a file * @param filePath - Path to the JSON file * @returns Promise resolving to parsed JSON */ static async readJSON(filePath: string): Promise { const content = await this.readFile(filePath); return JSON.parse(content); } /** * Write JSON to a file * @param filePath - Path to the file * @param data - Data to write as JSON * @param pretty - Whether to pretty-print JSON (default: true) * @returns Promise that resolves when file is written */ static async writeJSON(filePath: string, data: any, pretty = true): Promise { const jsonString = pretty ? JSON.stringify(data, null, 2) : JSON.stringify(data); await this.writeFile(filePath, jsonString); } /** * Copy a file from source to destination * @param source - Source file path * @param destination - Destination file path * @returns Promise that resolves when file is copied */ static async copyFile(source: string, destination: string): Promise { const destDir = plugins.path.dirname(destination); await this.ensureDir(destDir); await plugins.fs.promises.copyFile(source, destination); } /** * Move/rename a file * @param source - Source file path * @param destination - Destination file path * @returns Promise that resolves when file is moved */ static async moveFile(source: string, destination: string): Promise { const destDir = plugins.path.dirname(destination); await this.ensureDir(destDir); await plugins.fs.promises.rename(source, destination); } /** * Get file stats * @param filePath - Path to the file * @returns Promise resolving to file stats or null if doesn't exist */ static async getStats(filePath: string): Promise { try { return await plugins.fs.promises.stat(filePath); } catch (error: any) { if (error.code === 'ENOENT') { return null; } throw error; } } /** * List files in a directory * @param dirPath - Directory path * @returns Promise resolving to array of filenames */ static async listFiles(dirPath: string): Promise { try { return await plugins.fs.promises.readdir(dirPath); } catch (error: any) { if (error.code === 'ENOENT') { return []; } throw error; } } /** * List files in a directory with full paths * @param dirPath - Directory path * @returns Promise resolving to array of full file paths */ static async listFilesFullPath(dirPath: string): Promise { const files = await this.listFiles(dirPath); return files.map(file => plugins.path.join(dirPath, file)); } /** * Recursively list all files in a directory * @param dirPath - Directory path * @param fileList - Accumulator for file list (used internally) * @returns Promise resolving to array of all file paths */ static async listFilesRecursive(dirPath: string, fileList: string[] = []): Promise { const files = await this.listFiles(dirPath); for (const file of files) { const filePath = plugins.path.join(dirPath, file); const stats = await this.getStats(filePath); if (stats?.isDirectory()) { await this.listFilesRecursive(filePath, fileList); } else if (stats?.isFile()) { fileList.push(filePath); } } return fileList; } /** * Create a read stream for a file * @param filePath - Path to the file * @param options - Stream options * @returns Read stream */ static createReadStream(filePath: string, options?: Parameters[1]): plugins.fs.ReadStream { return plugins.fs.createReadStream(filePath, options); } /** * Create a write stream for a file * @param filePath - Path to the file * @param options - Stream options * @returns Write stream */ static createWriteStream(filePath: string, options?: Parameters[1]): plugins.fs.WriteStream { return plugins.fs.createWriteStream(filePath, options); } /** * Ensure a file exists, creating an empty file if necessary * @param filePath - Path to the file * @returns Promise that resolves when file is ensured */ static async ensureFile(filePath: string): Promise { const exists = await this.exists(filePath); if (!exists) { await this.writeFile(filePath, ''); } } /** * Check if a path is a directory * @param path - Path to check * @returns Promise resolving to true if directory, false otherwise */ static async isDirectory(path: string): Promise { const stats = await this.getStats(path); return stats?.isDirectory() ?? false; } /** * Check if a path is a file * @param path - Path to check * @returns Promise resolving to true if file, false otherwise */ static async isFile(path: string): Promise { const stats = await this.getStats(path); return stats?.isFile() ?? false; } }