import * as fs from "node:fs"; import * as path from "node:path"; import { pathToFileURL, fileURLToPath } from "node:url"; import { globalRootMarkers } from "../server.ts"; import { getServersForPath } from "./config.ts"; import type { ServerConfig } from "./types.ts"; import { UnsupportedExtensionError } from "./types.ts"; import { isOnPath } from "./util.ts"; // Re-Export - Centralizes the path-aware registry helper so callers can // import it from `./root.ts` alongside pickServer/findRoot. export { getServersForPath }; // Resolve File URI To Local Path export function uriToPath(uri: string): string { return fileURLToPath(uri); } // Resolve Local Path To File URI export function pathToUri(p: string): string { return pathToFileURL(path.resolve(p)).toString(); } // Server Availability Cache - Checked once per process lifetime per server. // Avoids repeated filesystem lookups on every tool call. const serverAvailability = new Map(); // Is Server Available - Returns true if the server binary is on PATH. // Result is cached for the lifetime of this process. export function isServerAvailable(server: ServerConfig): boolean { if (serverAvailability.has(server.id)) return serverAvailability.get(server.id)!; const available = isOnPath(server.command, process.env); serverAvailability.set(server.id, available); return available; } // Pick Server By File Extension - match[] entries are matched against the // file's extension (no dot). First available, non-diagnosticsOnly server wins. // Resolves the per-repo config from the file's directory before matching. export function pickServer(filePath: string): ServerConfig { const ext = path.extname(filePath).replace(/^\./, ""); const list = getServersForPath(filePath); const hit = list.find((s) => s.match.includes(ext) && !s.diagnosticsOnly && isServerAvailable(s)); if (!hit) { throw new UnsupportedExtensionError(`.${ext}`); } return hit; } // Find Server By ID - Looks up a ServerConfig by id within the registry // resolved for the given path. Throws if the id is not registered. export function findServerById(filePath: string, id: string): ServerConfig { const list = getServersForPath(filePath); const hit = list.find((s) => s.id === id); if (!hit) { throw new Error( `Unknown server ID: "${id}". Registered: ${list.map((s) => s.id).join(", ")}`, ); } return hit; } // Find Project Root By Walking Upward - stops at the first directory // containing any rootMarker. Falls back to the file's directory. export function findRoot(filePath: string, markers: string[]): string { let dir = path.dirname(path.resolve(filePath)); const { root } = path.parse(dir); const allMarkers = [...markers, ...globalRootMarkers]; while (true) { for (const m of allMarkers) { if (fs.existsSync(path.join(dir, m))) return dir; } if (dir === root) return path.dirname(path.resolve(filePath)); dir = path.dirname(dir); } }