UNPKG

@boundless-oss/atlas

Version:

Atlas - MCP Server for comprehensive startup project management

122 lines (102 loc) 3.67 kB
import { promises as fs } from 'fs'; import path from 'path'; export interface FileSystemAdapter { mkdir(dirPath: string, options?: { recursive?: boolean }): Promise<void>; writeFile(filePath: string, data: string): Promise<void>; readFile(filePath: string, encoding: string): Promise<string>; readdir(dirPath: string): Promise<string[]>; unlink(filePath: string): Promise<void>; access(filePath: string): Promise<void>; } export class NodeFileSystemAdapter implements FileSystemAdapter { async mkdir(dirPath: string, options?: { recursive?: boolean }): Promise<void> { await fs.mkdir(dirPath, options); } async writeFile(filePath: string, data: string): Promise<void> { await fs.writeFile(filePath, data); } async readFile(filePath: string, encoding: string): Promise<string> { return await fs.readFile(filePath, encoding as BufferEncoding); } async readdir(dirPath: string): Promise<string[]> { return await fs.readdir(dirPath); } async unlink(filePath: string): Promise<void> { await fs.unlink(filePath); } async access(filePath: string): Promise<void> { await fs.access(filePath); } } export class InMemoryFileSystemAdapter implements FileSystemAdapter { private files: Map<string, string> = new Map(); private directories: Set<string> = new Set(); async mkdir(dirPath: string, options?: { recursive?: boolean }): Promise<void> { if (options?.recursive) { // Handle both absolute and relative paths const normalizedPath = path.normalize(dirPath); const parts = normalizedPath.split(path.sep).filter(p => p); let currentPath = normalizedPath.startsWith(path.sep) ? path.sep : ''; for (const part of parts) { currentPath = path.join(currentPath, part); this.directories.add(currentPath); } } else { this.directories.add(dirPath); } } async writeFile(filePath: string, data: string): Promise<void> { const dir = path.dirname(filePath); // Skip directory check for root or current directory if (dir !== '.' && dir !== '/' && dir !== '') { if (!this.directories.has(dir)) { throw new Error(`ENOENT: no such file or directory, open '${filePath}'`); } } this.files.set(filePath, data); } async readFile(filePath: string, encoding: string): Promise<string> { const content = this.files.get(filePath); if (content === undefined) { throw new Error(`ENOENT: no such file or directory, open '${filePath}'`); } return content; } async readdir(dirPath: string): Promise<string[]> { if (!this.directories.has(dirPath)) { throw new Error(`ENOENT: no such file or directory, scandir '${dirPath}'`); } const files: string[] = []; for (const [filePath] of this.files) { if (path.dirname(filePath) === dirPath) { files.push(path.basename(filePath)); } } return files; } async unlink(filePath: string): Promise<void> { if (!this.files.has(filePath)) { throw new Error(`ENOENT: no such file or directory, unlink '${filePath}'`); } this.files.delete(filePath); } async access(filePath: string): Promise<void> { if (!this.files.has(filePath)) { throw new Error(`ENOENT: no such file or directory, access '${filePath}'`); } } // Test helper methods clear(): void { this.files.clear(); this.directories.clear(); } getFileCount(): number { return this.files.size; } hasFile(filePath: string): boolean { return this.files.has(filePath); } hasDirectory(dirPath: string): boolean { return this.directories.has(dirPath); } }