@boundless-oss/atlas
Version:
Atlas - MCP Server for comprehensive startup project management
122 lines (102 loc) • 3.67 kB
text/typescript
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);
}
}