zed.mcp
Version:
MCP server for project analysis, AI rules reading, and dependency checking
125 lines (115 loc) • 3.28 kB
text/typescript
import fs from "fs/promises";
import path from "path";
/**
* Default patterns for an LLM to ignore
*/
const DEFAULT_IGNORE_PATTERNS = [
"node_modules",
".git",
"dist",
"build",
".DS_Store",
"*.log",
"*.lock",
".env",
];
/**
* Parses the .gitignore file from the project path and combines it with
* default ignore patterns.
*/
export async function getIgnorePatterns(projectPath: string): Promise<string[]> {
const gitignorePath = path.join(projectPath, ".gitignore");
try {
const gitignoreContent = await fs.readFile(gitignorePath, "utf-8");
const gitignorePatterns = gitignoreContent
.split("\n")
.map((line) => line.trim())
.filter((line) => line && !line.startsWith("#"));
return [...DEFAULT_IGNORE_PATTERNS, ...gitignorePatterns];
} catch (error) {
return DEFAULT_IGNORE_PATTERNS;
}
}
/**
* Checks if a given file path should be ignored based on patterns.
* NOTE: Simplified implementation - doesn't support complex glob patterns.
*/
export function isIgnored(relativePath: string, ignorePatterns: string[]): boolean {
const pathSegments = relativePath.split(path.sep);
return ignorePatterns.some((pattern) => {
// Handle wildcard patterns like *.log
if (pattern.startsWith("*.")) {
const extension = pattern.substring(1);
return relativePath.endsWith(extension);
}
// Handle directory or file names
return pathSegments.includes(pattern.replace("/", ""));
});
}
/**
* Recursively traverses directories to build an indented string representation
* of the project structure.
*/
export async function buildStructureString(
dirPath: string,
projectPath: string,
ignorePatterns: string[],
indent = "",
): Promise<string> {
let structure = "";
let entries;
try {
entries = await fs.readdir(dirPath, { withFileTypes: true });
} catch (error) {
console.error(`Could not read directory: ${dirPath}`);
return "";
}
for (const entry of entries) {
const fullPath = path.join(dirPath, entry.name);
const relativePath = path.relative(projectPath, fullPath);
if (isIgnored(relativePath, ignorePatterns)) {
continue;
}
if (entry.isDirectory()) {
structure += `${indent}${entry.name}/\n`;
structure += await buildStructureString(
fullPath,
projectPath,
ignorePatterns,
indent + " ",
);
} else {
structure += `${indent}${entry.name}\n`;
}
}
return structure;
}
/**
* Checks if a path exists and is a directory
*/
export async function isDirectory(dirPath: string): Promise<boolean> {
try {
const stats = await fs.stat(dirPath);
return stats.isDirectory();
} catch (error) {
return false;
}
}
/**
* Reads a JSON file and parses it
*/
export async function readJsonFile<T = any>(filePath: string): Promise<T> {
const content = await fs.readFile(filePath, "utf-8");
return JSON.parse(content);
}
/**
* Checks if a file exists
*/
export async function fileExists(filePath: string): Promise<boolean> {
try {
await fs.access(filePath);
return true;
} catch (error) {
return false;
}
}