@boundless-oss/atlas
Version:
Atlas - MCP Server for comprehensive startup project management
141 lines (112 loc) • 4.17 kB
text/typescript
import { promises as fs } from 'fs';
import path from 'path';
export interface FileSystemAdapter {
readdir(dirPath: string): Promise<any[]>;
stat(filePath: string): Promise<{ isDirectory(): boolean }>;
access(filePath: string): Promise<void>;
readFile(filePath: string, encoding: string): Promise<string>;
writeFile(filePath: string, content: string): Promise<void>;
}
export class NodeFileSystemAdapter implements FileSystemAdapter {
async readdir(dirPath: string): Promise<any[]> {
return await fs.readdir(dirPath, { withFileTypes: true });
}
async stat(filePath: string): Promise<{ isDirectory(): boolean }> {
return await fs.stat(filePath);
}
async access(filePath: string): Promise<void> {
await fs.access(filePath);
}
async readFile(filePath: string, encoding: string): Promise<string> {
return await fs.readFile(filePath, { encoding: encoding as BufferEncoding });
}
async writeFile(filePath: string, content: string): Promise<void> {
await fs.writeFile(filePath, content);
}
}
export class InMemoryFileSystemAdapter implements FileSystemAdapter {
private directories: Map<string, any[]> = new Map();
private files: Map<string, string> = new Map();
private dirStats: Set<string> = new Set();
constructor() {
// Initialize with root directory
this.directories.set('/', []);
this.dirStats.add('/');
}
async readdir(dirPath: string): Promise<any[]> {
const normalizedPath = path.normalize(dirPath);
const entries = this.directories.get(normalizedPath);
if (!entries) {
throw new Error(`ENOENT: no such file or directory, scandir '${dirPath}'`);
}
return entries;
}
async stat(filePath: string): Promise<{ isDirectory(): boolean }> {
const normalizedPath = path.normalize(filePath);
return {
isDirectory: () => this.dirStats.has(normalizedPath)
};
}
async access(filePath: string): Promise<void> {
const normalizedPath = path.normalize(filePath);
if (!this.files.has(normalizedPath) && !this.dirStats.has(normalizedPath)) {
throw new Error(`ENOENT: no such file or directory, access '${filePath}'`);
}
}
async readFile(filePath: string, encoding: string): Promise<string> {
const normalizedPath = path.normalize(filePath);
const content = this.files.get(normalizedPath);
if (content === undefined) {
throw new Error(`ENOENT: no such file or directory, open '${filePath}'`);
}
return content;
}
async writeFile(filePath: string, content: string): Promise<void> {
const normalizedPath = path.normalize(filePath);
this.files.set(normalizedPath, content);
}
// Test helper methods
addDirectory(dirPath: string, entries: Array<{ name: string; isDirectory: boolean }>): void {
const normalizedPath = path.normalize(dirPath);
// Add the directory itself
this.dirStats.add(normalizedPath);
// Create mock directory entries
const mockEntries = entries.map(entry => ({
name: entry.name,
isDirectory: () => entry.isDirectory
}));
this.directories.set(normalizedPath, mockEntries);
// Add subdirectories to dirStats
entries.forEach(entry => {
if (entry.isDirectory) {
const subPath = path.join(normalizedPath, entry.name);
this.dirStats.add(subPath);
}
});
}
addGitDirectory(dirPath: string): void {
const normalizedPath = path.normalize(dirPath);
const gitPath = path.join(normalizedPath, '.git');
// Mark the .git path as accessible
this.dirStats.add(gitPath);
// Also update parent directory entries if it exists
const parentEntries = this.directories.get(normalizedPath);
if (parentEntries) {
const hasGit = parentEntries.some(e => e.name === '.git');
if (!hasGit) {
parentEntries.push({
name: '.git',
isDirectory: () => true
});
}
}
}
clear(): void {
this.directories.clear();
this.files.clear();
this.dirStats.clear();
// Re-initialize with root
this.directories.set('/', []);
this.dirStats.add('/');
}
}