Skip to main content
Glama
nrwl

Nx MCP Server

Official
by nrwl
config-file-codelens-provider.ts8.65 kB
import { getNxWorkspacePath } from '@nx-console/vscode-configuration'; import { onWorkspaceRefreshed } from '@nx-console/vscode-lsp-client'; import { getProjectByRoot, getSourceMapFilesToProjectsMap, getTargetsForConfigFile, } from '@nx-console/vscode-nx-workspace'; import { CliTaskProvider } from '@nx-console/vscode-tasks'; import { getTelemetry } from '@nx-console/vscode-telemetry'; import { NxCodeLensProvider, registerCodeLensProvider, createProjectTargetString, } from '@nx-console/vscode-utils'; import { relative } from 'path'; import { Node, ScriptTarget, createSourceFile, isExportAssignment, isExportDeclaration, isImportDeclaration, } from 'typescript'; import { CancellationToken, CodeLens, Event, EventEmitter, ExtensionContext, Range, TextDocument, commands, window, } from 'vscode'; export const CODELENS_RUN_TARGET_COMMAND = 'nxConsole.config-codelens.run'; const CODELENS_OPEN_RUN_QUICKPICK_COMMAND = 'nxConsole.config-codelens.open-run-quickpick'; export class ConfigFileCodelensProvider implements NxCodeLensProvider { CODELENS_PATTERN = { scheme: 'file', }; constructor( public workspaceRoot: string, public sourceMapFilesToProjectsMap: Record<string, string[]>, ) {} private changeEvent = new EventEmitter<void>(); public get onDidChangeCodeLenses(): Event<void> { return this.changeEvent.event; } public refresh(): void { this.changeEvent.fire(); } async provideCodeLenses( document: TextDocument, token: CancellationToken, ): Promise<CodeLens[]> { const relativePath = relative(this.workspaceRoot, document.fileName); if ( relativePath.endsWith('project.json') || relativePath.endsWith('package.json') ) { return []; } // TODO: UPDATE TO HANDLE MULTIPLE PROJECTS const projectRoot = this.sourceMapFilesToProjectsMap[relativePath]?.[0]; if (!projectRoot) { return []; } const location = this.getCodeLensLocation(document); return [ new RunTargetCodeLens(projectRoot, relativePath, location), new OpenPDVCodeLens(document, projectRoot, relativePath, location), ]; } private getCodeLensLocation(document: TextDocument): Range { try { if (['typescript', 'javascript'].includes(document.languageId)) { const configFile = createSourceFile( document.fileName, document.getText(), { languageVersion: ScriptTarget.Latest, }, ); let firstNonImportNode: Node | undefined = undefined; for (const node of configFile.statements) { if (isExportDeclaration(node) || isExportAssignment(node)) { const pos = document.positionAt(node.getStart(configFile)); return new Range(pos, pos); } if (!firstNonImportNode && !isImportDeclaration(node)) { firstNonImportNode = node; } } if (firstNonImportNode) { const pos = document.positionAt( firstNonImportNode.getStart(configFile), ); return new Range(pos, pos); } } return new Range(0, 0, 0, 0); } catch (e) { return new Range(0, 0, 0, 0); } } async resolveCodeLens( codeLens: RunTargetCodeLens | OpenPDVCodeLens, token: CancellationToken, ): Promise<CodeLens> { if (OpenPDVCodeLens.is(codeLens)) { return await this.resolveOpenPDVCodeLens(codeLens); } else { return await this.resolveRunTargetCodeLens(codeLens); } } private async resolveRunTargetCodeLens( codeLens: RunTargetCodeLens, ): Promise<CodeLens> { const project = await getProjectByRoot(codeLens.projectRoot); const targets = await getTargetsForConfigFile( project?.name ?? '', codeLens.filePath, ); const targetNames = Object.keys(targets ?? {}); if (targetNames.length === 1) { return { ...codeLens, command: { title: `$(play) Run ${createProjectTargetString(project?.name ?? '', targetNames[0])} via nx`, command: CODELENS_RUN_TARGET_COMMAND, arguments: [project?.name ?? '', targetNames[0]], }, }; } else { const targetInfo = targetNames.map((targetName) => { const target = targets?.[targetName]; const command = target?.command ?? target?.options.command ?? target?.executor; return { targetName, command, }; }); return { ...codeLens, command: { title: `$(play) Run ${project?.name} targets via nx`, command: CODELENS_OPEN_RUN_QUICKPICK_COMMAND, arguments: [targetInfo, project?.name ?? '', codeLens.filePath], }, }; } } private async resolveOpenPDVCodeLens( codeLens: OpenPDVCodeLens, ): Promise<CodeLens> { const project = await getProjectByRoot(codeLens.projectRoot); const targets = await getTargetsForConfigFile( project?.name ?? '', codeLens.filePath, ); const targetNames = Object.keys(targets ?? {}); return { ...codeLens, command: { title: `$(open-preview) Open Project Details View`, command: 'nx.project-details.openToSide', arguments: [ { document: codeLens.document, expandTarget: targetNames[0], }, ], }, }; } static async register(context: ExtensionContext) { const workspaceRoot = getNxWorkspacePath(); const initialSourceMapFilesToProjectsMap = (await getSourceMapFilesToProjectsMap()) ?? {}; const codeLensProvider = new ConfigFileCodelensProvider( workspaceRoot, initialSourceMapFilesToProjectsMap, ); onWorkspaceRefreshed(async () => { const updatedWorkspaceRoot = getNxWorkspacePath(); const updatedSourceMapFilesToProjectMap = (await getSourceMapFilesToProjectsMap()) ?? {}; codeLensProvider.workspaceRoot = updatedWorkspaceRoot; codeLensProvider.sourceMapFilesToProjectsMap = updatedSourceMapFilesToProjectMap; codeLensProvider.refresh(); }); registerCodeLensProvider(codeLensProvider); context.subscriptions.push( commands.registerCommand( CODELENS_RUN_TARGET_COMMAND, (project: string, target: string) => { getTelemetry().logUsage('tasks.run', { source: 'codelens', }); CliTaskProvider.instance.executeTask({ command: 'run', positional: createProjectTargetString(project, target), flags: [], }); }, ), ); commands.registerCommand( CODELENS_OPEN_RUN_QUICKPICK_COMMAND, ( targets: { targetName: string; command: string }[], projectName: string, fileName: string, ) => { getTelemetry().logUsage('misc.open-project-details-codelens'); if (targets.length === 0) { window .showErrorMessage( `Couldn't find any targets for ${fileName}. Check the Nx Console Client output channel for any errors`, 'Open Output', ) .then((value) => { if (value === 'Open Output') { commands.executeCommand('workbench.action.output.toggleOutput'); } }); return; } window .showQuickPick( targets.map((target) => ({ label: target.targetName, description: target.command, })), { placeHolder: `Select nx target to run for ${fileName}`, }, ) .then((selected) => { if (selected) { CliTaskProvider.instance.executeTask({ command: 'run', positional: createProjectTargetString( projectName, selected.label, ), flags: [], }); } }); }, ); } } class RunTargetCodeLens extends CodeLens { constructor( public projectRoot: string, public filePath: string, range: Range, ) { super(range); } } class OpenPDVCodeLens extends CodeLens { constructor( public document: TextDocument, public projectRoot: string, public filePath: string, range: Range, ) { super(range); } static is(codeLens: CodeLens): codeLens is OpenPDVCodeLens { return ( (codeLens as any).projectRoot && (codeLens as any).filePath && (codeLens as any).document ); } }

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