@memory-bank/mcp
Version:
Memory-enabled Co-Pilot (MCP) server for managing project documentation and context
376 lines • 15.8 kB
JavaScript
import path from 'node:path';
import { InfrastructureError, InfrastructureErrorCodes } from '../../../shared/errors/InfrastructureError.js';
import { logger } from '../../../shared/utils/logger.js';
import { FileSystemMemoryBankRepositoryBase } from './FileSystemMemoryBankRepositoryBase.js';
/**
* Component responsible for path-related operations
*/
export class PathOperations extends FileSystemMemoryBankRepositoryBase {
basePath;
configProvider;
/**
* Constructor
* @param basePath Base path
* @param fileSystemService File system service
* @param configProvider Configuration provider
*/
constructor(basePath, fileSystemService, configProvider) {
super(fileSystemService, configProvider);
this.basePath = basePath;
this.configProvider = configProvider;
}
/**
* Get the base path for a branch
* @param branchInfo Branch information
* @returns Base path for the branch
*/
getBranchBasePath(_branchInfo) {
// branchInfo parameter is currently unused but kept for future extensibility
return this.basePath;
}
/**
* Get the global base path
* @returns Global base path
*/
getGlobalBasePath() {
return this.basePath;
}
/**
* Resolve a path
* @param documentPath Document path
* @returns Full file path
*/
resolvePath(documentPath) {
// Normalize path
const normalizedPath = path.normalize(documentPath);
// Check for path traversal attacks
if (normalizedPath.startsWith('..')) {
throw new InfrastructureError(InfrastructureErrorCodes.FILE_SYSTEM_ERROR, `Invalid document path: ${documentPath}. Path cannot traverse outside the base directory.`);
}
// Resolve full path
return path.join(this.basePath, normalizedPath);
}
/**
* Resolve a branch path
* @param branchInfo Branch information
* @param documentPath Document path
* @returns Full file path
*/
resolveBranchPath(branchInfo, documentPath) {
const branchBasePath = this.getBranchBasePath(branchInfo);
return this.resolvePathWithBase(branchBasePath, documentPath);
}
/**
* Resolve a path using a specified base path
* @param basePath Base path
* @param documentPath Document path
* @returns Full file path
*/
resolvePathWithBase(basePath, documentPath) {
// Normalize path
const normalizedPath = path.normalize(documentPath);
// Check for path traversal attacks
if (normalizedPath.startsWith('..')) {
throw new InfrastructureError(InfrastructureErrorCodes.FILE_SYSTEM_ERROR, `Invalid document path: ${documentPath}. Path cannot traverse outside the base directory.`);
}
// Resolve full path
return path.join(basePath, normalizedPath);
}
/**
* Check if a path exists
* @param documentPath Document path
* @returns true if the path exists, false otherwise
*/
async exists(documentPath) {
try {
const fullPath = this.resolvePath(documentPath);
return await this.fileExists(fullPath);
}
catch (error) {
throw new InfrastructureError(InfrastructureErrorCodes.FILE_SYSTEM_ERROR, `Failed to check if path exists: ${documentPath}`, { originalError: error });
}
}
/**
* Check if a branch path exists
* @param branchInfo Branch information
* @param documentPath Document path
* @returns true if the path exists, false otherwise
*/
async branchPathExists(branchInfo, documentPath) {
try {
const fullPath = this.resolveBranchPath(branchInfo, documentPath);
return await this.fileExists(fullPath);
}
catch (error) {
throw new InfrastructureError(InfrastructureErrorCodes.FILE_SYSTEM_ERROR, `Failed to check if branch path exists: ${branchInfo.name}/${documentPath}`, { originalError: error });
}
}
/**
* Create a directory
* @param directoryPath Directory path
*/
async createDirectory(directoryPath) {
try {
const fullPath = this.resolvePath(directoryPath);
await super.createDirectory(fullPath);
}
catch (error) {
if (error instanceof InfrastructureError) {
throw error;
}
throw new InfrastructureError(InfrastructureErrorCodes.FILE_SYSTEM_ERROR, `Failed to create directory: ${directoryPath}`, { originalError: error });
}
}
/**
* Create a branch directory
* @param branchInfo Branch information
* @param directoryPath Directory path
*/
async createBranchDirectory(branchInfo, directoryPath) {
try {
const fullPath = this.resolveBranchPath(branchInfo, directoryPath);
await super.createDirectory(fullPath);
logger.debug(`Created directory for branch ${branchInfo.name}: ${directoryPath}`);
}
catch (error) {
if (error instanceof InfrastructureError) {
throw error;
}
throw new InfrastructureError(InfrastructureErrorCodes.FILE_SYSTEM_ERROR, `Failed to create branch directory: ${branchInfo.name}/${directoryPath}`, { originalError: error });
}
}
/**
* Check if a directory exists
* @param directoryPath Directory path
* @returns true if the directory exists, false otherwise
*/
async directoryExists(directoryPath) {
try {
const fullPath = this.resolvePath(directoryPath);
return await super.directoryExists(fullPath);
}
catch (error) {
throw new InfrastructureError(InfrastructureErrorCodes.FILE_SYSTEM_ERROR, `Failed to check if directory exists: ${directoryPath}`, { originalError: error });
}
}
/**
* Check if a branch directory exists
* @param branchInfo Branch information
* @param directoryPath Directory path
* @returns true if the directory exists, false otherwise
*/
async branchDirectoryExists(branchInfo, directoryPath) {
try {
const fullPath = this.resolveBranchPath(branchInfo, directoryPath);
return await super.directoryExists(fullPath);
}
catch (error) {
throw new InfrastructureError(InfrastructureErrorCodes.FILE_SYSTEM_ERROR, `Failed to check if branch directory exists: ${branchInfo.name}/${directoryPath}`, { originalError: error });
}
}
/**
* Get a list of files
* @param directoryPath Directory path
* @returns Array of file paths
*/
async listFiles(directoryPath) {
try {
const fullPath = this.resolvePath(directoryPath);
return await super.listFiles(fullPath);
}
catch (error) {
throw new InfrastructureError(InfrastructureErrorCodes.FILE_SYSTEM_ERROR, `Failed to list files: ${directoryPath}`, { originalError: error });
}
}
/**
* Get a list of files in a branch
* @param branchInfo Branch information
* @param directoryPath Directory path
* @returns Array of file paths
*/
async listBranchFiles(branchInfo, directoryPath) {
try {
const fullPath = this.resolveBranchPath(branchInfo, directoryPath);
return await super.listFiles(fullPath);
}
catch (error) {
throw new InfrastructureError(InfrastructureErrorCodes.FILE_SYSTEM_ERROR, `Failed to list files in branch: ${branchInfo.name}/${directoryPath}`, { originalError: error });
}
}
/**
* Copy a file
* @param sourcePath Source path
* @param destinationPath Destination path
*/
async copyFile(sourcePath, destinationPath) {
try {
const sourceFullPath = this.resolvePath(sourcePath);
const destFullPath = this.resolvePath(destinationPath);
// Ensure source file exists
const exists = await this.fileExists(sourceFullPath);
if (!exists) {
throw new InfrastructureError(InfrastructureErrorCodes.FILE_NOT_FOUND, `Source file not found: ${sourcePath}`);
}
// Ensure destination directory exists
const destDir = path.dirname(destFullPath);
await super.createDirectory(destDir);
// Read source file content
const content = await this.readFile(sourceFullPath);
// Write file to new location
await this.writeFile(destFullPath, content);
}
catch (error) {
if (error instanceof InfrastructureError) {
throw error;
}
throw new InfrastructureError(InfrastructureErrorCodes.FILE_SYSTEM_ERROR, `Failed to copy file from ${sourcePath} to ${destinationPath}`, { originalError: error });
}
}
/**
* Copy a file between branches
* @param sourceBranchInfo Source branch information
* @param sourcePath Source path
* @param destinationBranchInfo Destination branch information
* @param destinationPath Destination path
*/
async copyFileBetweenBranches(sourceBranchInfo, sourcePath, destinationBranchInfo, destinationPath) {
try {
const sourceFullPath = this.resolveBranchPath(sourceBranchInfo, sourcePath);
const destFullPath = this.resolveBranchPath(destinationBranchInfo, destinationPath);
// Ensure source file exists
const exists = await this.fileExists(sourceFullPath);
if (!exists) {
throw new InfrastructureError(InfrastructureErrorCodes.FILE_NOT_FOUND, `Source file not found in branch ${sourceBranchInfo.name}: ${sourcePath}`);
}
// Ensure destination directory exists
const destDir = path.dirname(destFullPath);
await super.createDirectory(destDir);
// Read source file content
const content = await this.readFile(sourceFullPath);
// Write file to new location
await this.writeFile(destFullPath, content);
logger.debug(`Copied file from branch ${sourceBranchInfo.name}/${sourcePath} to ${destinationBranchInfo.name}/${destinationPath}`);
}
catch (error) {
if (error instanceof InfrastructureError) {
throw error;
}
throw new InfrastructureError(InfrastructureErrorCodes.FILE_SYSTEM_ERROR, `Failed to copy file between branches: ${sourceBranchInfo.name}/${sourcePath} to ${destinationBranchInfo.name}/${destinationPath}`, { originalError: error });
}
}
/**
* Move a file
* @param sourcePath Source path
* @param destinationPath Destination path
*/
async moveFile(sourcePath, destinationPath) {
try {
// First copy the file
await this.copyFile(sourcePath, destinationPath);
// If copy succeeds, delete the original file
const sourceFullPath = this.resolvePath(sourcePath);
await super.deleteFile(sourceFullPath);
logger.debug(`Moved file from ${sourcePath} to ${destinationPath}`);
}
catch (error) {
if (error instanceof InfrastructureError) {
throw error;
}
throw new InfrastructureError(InfrastructureErrorCodes.FILE_SYSTEM_ERROR, `Failed to move file from ${sourcePath} to ${destinationPath}`, { originalError: error });
}
}
/**
* Move a file between branches
* @param sourceBranchInfo Source branch information
* @param sourcePath Source path
* @param destinationBranchInfo Destination branch information
* @param destinationPath Destination path
*/
async moveFileBetweenBranches(sourceBranchInfo, sourcePath, destinationBranchInfo, destinationPath) {
try {
// First copy the file
await this.copyFileBetweenBranches(sourceBranchInfo, sourcePath, destinationBranchInfo, destinationPath);
// If copy succeeds, delete the original file
const sourceFullPath = this.resolveBranchPath(sourceBranchInfo, sourcePath);
await super.deleteFile(sourceFullPath);
logger.debug(`Moved file from branch ${sourceBranchInfo.name}/${sourcePath} to ${destinationBranchInfo.name}/${destinationPath}`);
}
catch (error) {
if (error instanceof InfrastructureError) {
throw error;
}
throw new InfrastructureError(InfrastructureErrorCodes.FILE_SYSTEM_ERROR, `Failed to move file between branches: ${sourceBranchInfo.name}/${sourcePath} to ${destinationBranchInfo.name}/${destinationPath}`, { originalError: error });
}
}
/**
* Get a list of files within a specified directory
* @param directoryPath Directory path
* @param allowedExtensions Array of allowed extensions (optional, defaults to all files)
* @returns Array of file paths
*/
async listFilesInDirectory(directoryPath, allowedExtensions = []) {
try {
const fullPath = this.resolvePath(directoryPath);
// Check if directory exists
const exists = await this.directoryExists(fullPath);
if (!exists) {
return [];
}
// Get list of files
const allFiles = await super.listFiles(fullPath);
// Filter by extension
if (allowedExtensions.length === 0) {
return allFiles;
}
return allFiles.filter(file => {
const ext = path.extname(file);
return allowedExtensions.includes(ext);
});
}
catch (error) {
throw new InfrastructureError(InfrastructureErrorCodes.FILE_SYSTEM_ERROR, `Failed to list files in directory: ${directoryPath}`, { originalError: error });
}
}
/**
* Filter an array of DocumentPaths
* @param paths Array of DocumentPaths
* @param pattern Filter pattern (glob-like)
* @returns Filtered array of DocumentPaths
*/
filterPaths(paths, pattern) {
try {
// Simple prefix/suffix matching
let filtered = paths;
// Filter by extension
if (pattern.startsWith('*.')) {
const extension = pattern.substring(1); // '*.json' -> '.json'
filtered = paths.filter(p => p.value.endsWith(extension));
}
// Filter by directory
else if (pattern.endsWith('/*')) {
const dir = pattern.substring(0, pattern.length - 1); // 'dir/*' -> 'dir/'
filtered = paths.filter(p => p.value.startsWith(dir));
}
// Prefix match
else if (pattern.endsWith('*')) {
const prefix = pattern.substring(0, pattern.length - 1); // 'prefix*' -> 'prefix'
filtered = paths.filter(p => p.value.startsWith(prefix));
}
// Suffix match
else if (pattern.startsWith('*')) {
const suffix = pattern.substring(1); // '*suffix' -> 'suffix'
filtered = paths.filter(p => p.value.endsWith(suffix));
}
// Exact match
else {
filtered = paths.filter(p => p.value === pattern);
}
return filtered;
}
catch (error) {
logger.error(`Error filtering paths with pattern ${pattern}:`, error);
return paths;
}
}
}
//# sourceMappingURL=PathOperations.js.map