Skip to main content
Glama
nrwl

Nx MCP Server

Official
by nrwl
get-project-by-path.ts6.54 kB
import { directoryExists } from '@nx-console/shared-file-system'; import type { ProjectConfiguration } from 'nx/src/devkit-exports'; import { isAbsolute, join, normalize, relative, sep } from 'path'; import { nxWorkspace } from '@nx-console/shared-nx-workspace-info'; import { platform } from 'os'; import { lspLogger } from '@nx-console/language-server-utils'; let _rootProjectMap: Record<string, ProjectConfiguration> | undefined; export function resetProjectPathCache() { _rootProjectMap = undefined; } export async function getProjectByPath( path: string, workspacePath: string, ): Promise<ProjectConfiguration | undefined> { path = normalize(path); // windows paths like /c:/Users/... aren't correctly picked up by normalize() so we need to handle them ourselves path = path.startsWith(sep) && platform() === 'win32' ? path.substring(1) : path; const projectsMap = await getProjectsByPaths([path], workspacePath); return projectsMap?.[path] || undefined; } export async function getProjectByRoot( rootPath: string, workspacePath: string, ): Promise<ProjectConfiguration | undefined> { if (_rootProjectMap && _rootProjectMap[rootPath]) { return _rootProjectMap[rootPath]; } const { projectGraph } = await nxWorkspace(workspacePath, lspLogger); const rootProjectMap: Record<string, ProjectConfiguration> = {}; const projectEntries = Object.entries(projectGraph.nodes); for (const [, projectConfig] of projectEntries) { rootProjectMap[projectConfig.data.root] = projectConfig.data; } _rootProjectMap = rootProjectMap; return _rootProjectMap?.[rootPath]; } export async function getProjectsByPaths( paths: string[] | undefined, workspacePath: string, ): Promise<Record<string, ProjectConfiguration> | undefined> { if (!paths) { return undefined; } // windows paths like /c:/Users/... aren't correctly picked up by normalize() so we need to handle them ourselves const pathsNormalized = paths .map((p) => normalize(p)) .map((p) => p.startsWith(sep) && platform() === 'win32' ? p.substring(1) : p, ); workspacePath = normalize(workspacePath); workspacePath = workspacePath.startsWith(sep) && platform() === 'win32' ? workspacePath.substring(1) : workspacePath; const { projectGraph, projectFileMap } = await nxWorkspace( workspacePath, lspLogger, ); const pathsMap = new Map< string, { relativePath: string; isDirectory: boolean } >(); for (const path of pathsNormalized) { // lspLogger.log( // `workspacePath: ${workspacePath}, path ${path}, relative: ${relative( // workspacePath, // path // )}, isDirectory: ${await directoryExists(path)}` // ); pathsMap.set(path, { relativePath: relative(workspacePath, path), isDirectory: await directoryExists(path), }); } const projectEntries = Object.entries(projectGraph.nodes); const foundProjects: Map<string, ProjectConfiguration> = new Map(); for (const [projectName, projectConfig] of projectEntries) { // If there is no files array, it's an old version of Nx and we need backwards compatibility if (!projectFileMap?.[projectName]) { new Map(pathsMap).forEach((_, path) => { const foundProject = findByFilePath( [projectName, projectConfig.data], workspacePath, path, ); if (foundProject) { foundProjects.set(path, foundProject); pathsMap.delete(path); } }); continue; } // project check for directories new Map(pathsMap).forEach(({ relativePath, isDirectory }, path) => { if (!isDirectory) return; const isChildOfRoot = isChildOrEqual( projectConfig.data.root, relativePath, ); const relativeRootConfig = projectConfig.data.sourceRoot ? relative(workspacePath, projectConfig.data.sourceRoot) : undefined; const isChildOfRootConfig = relativeRootConfig && isChildOrEqual(relativeRootConfig, relativePath); if (isChildOfRoot || isChildOfRootConfig) { foundProjects.set(path, projectConfig.data); pathsMap.delete(path); } }); // iterate over the project files once and find all the paths that match const nonDirectoryPaths = [...pathsMap.entries()].filter( ([_, { isDirectory }]) => !isDirectory, ); projectFileMap?.[projectName]?.forEach(({ file }) => { for (const [path, { relativePath }] of nonDirectoryPaths) { if (normalize(file) === normalize(relativePath)) { foundProjects.set(path, projectConfig.data); pathsMap.delete(path); } } }); if (pathsMap.size === 0) { break; } } // if a directory is not found in any projects & there's a root project, use that if (pathsMap.size > 0) { const rootProject = projectEntries.find( ([, projectConfig]) => projectConfig.data.root === '.', ); if (rootProject) { new Map(pathsMap).forEach(({ isDirectory }, path) => { if (!isDirectory) { return; } foundProjects.set(path, rootProject[1].data); pathsMap.delete(path); }); } } return Object.fromEntries(foundProjects); } /** This is only used for backwards compatibility */ function findByFilePath( entry: [string, ProjectConfiguration] | undefined, workspacePath: string, selectedPath: string, ) { if (!entry) { return null; } let perfectMatchEntry: [string, ProjectConfiguration] | undefined; let secondaryMatchEntry: [string, ProjectConfiguration] | undefined; const [, projectConfiguration] = entry; const fullProjectPath = join( workspacePath, // If root is empty, that means we're in an angular project with the old ng workspace setup. Otherwise use the sourceRoot projectConfiguration.root || projectConfiguration.sourceRoot || '', ); if (fullProjectPath === selectedPath) { perfectMatchEntry = entry; } const relativePath = relative(fullProjectPath, selectedPath); if ( relativePath && !relativePath.startsWith('..') && !isAbsolute(relativePath) ) { secondaryMatchEntry = entry; } entry = perfectMatchEntry ?? secondaryMatchEntry; return entry ? { name: entry[0], ...entry[1] } : null; } function isChildOrEqual(parent: string, child: string) { const p = parent.endsWith(sep) ? parent : parent + sep; const c = child.endsWith(sep) ? child : child + sep; return normalize(c).startsWith(normalize(p)); }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/nrwl/nx-console'

If you have feedback or need assistance with the MCP directory API, please join our Discord server