Skip to main content
Glama
nrwl

Nx MCP Server

Official
by nrwl
load-html.ts12 kB
import { getNxVersion, getProjectGraphOutput, } from '@nx-console/vscode-nx-workspace'; import { vscodeLogger } from '@nx-console/vscode-output-channels'; import { Uri, WebviewPanel, workspace } from 'vscode'; import { MessageType } from './graph-message-type'; import { gte } from '@nx-console/nx-version'; const html = String.raw; export function loadNoProject() { return html` <script> const vscodeApi = acquireVsCodeApi(); function refresh() { vscodeApi.postMessage({ command: 'refresh', }); } </script> <p> Unable to find the selected project in the workspace. Please make sure that node_modules is installed and that the Nx projects are loaded properly. </p> <p> If node_modules are installed, click <a onclick="refresh()" style="cursor: pointer">here</a> to reload the projects in the workspace. </p> `; } export function loadError(errorMessage: string | null) { if (errorMessage) { return html` <style> pre { white-space: pre-wrap; border-radius: 5px; border: 2px solid var(--vscode-editorWidget-border); padding: 20px; } </style> <p>Unable to load the project graph. The following error occured:</p> <pre>${errorMessage}</pre> `; } return html` <p>Unable to load the project graph. Please check the output for errors.</p> `; } export function loadSpinner() { return html` <style> .lds-roller { display: inline-block; position: relative; width: 80px; height: 80px; } .lds-roller div { animation: lds-roller 1.2s cubic-bezier(0.5, 0, 0.5, 1) infinite; transform-origin: 40px 40px; } .lds-roller div:after { content: ' '; display: block; position: absolute; width: 7px; height: 7px; border-radius: 50%; background: var(--vscode-editor-foreground); margin: -4px 0 0 -4px; } .lds-roller div:nth-child(1) { animation-delay: -0.036s; } .lds-roller div:nth-child(1):after { top: 63px; left: 63px; } .lds-roller div:nth-child(2) { animation-delay: -0.072s; } .lds-roller div:nth-child(2):after { top: 68px; left: 56px; } .lds-roller div:nth-child(3) { animation-delay: -0.108s; } .lds-roller div:nth-child(3):after { top: 71px; left: 48px; } .lds-roller div:nth-child(4) { animation-delay: -0.144s; } .lds-roller div:nth-child(4):after { top: 72px; left: 40px; } .lds-roller div:nth-child(5) { animation-delay: -0.18s; } .lds-roller div:nth-child(5):after { top: 71px; left: 32px; } .lds-roller div:nth-child(6) { animation-delay: -0.216s; } .lds-roller div:nth-child(6):after { top: 68px; left: 24px; } .lds-roller div:nth-child(7) { animation-delay: -0.252s; } .lds-roller div:nth-child(7):after { top: 63px; left: 17px; } .lds-roller div:nth-child(8) { animation-delay: -0.288s; } .lds-roller div:nth-child(8):after { top: 56px; left: 12px; } @keyframes lds-roller { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } } main { display: flex; align-items: center; justify-content: center; height: 100vh; } </style> <main> <div class="lds-roller"> <div></div> <div></div> <div></div> <div></div> <div></div> <div></div> <div></div> <div></div> </div> </main> `; } export async function loadHtml(panel: WebviewPanel) { const projectGraphOutput = await getProjectGraphOutput(); if (!projectGraphOutput) { return ''; } vscodeLogger.log('Loading project graph from ' + projectGraphOutput.fullPath); const rootUri = Uri.file(projectGraphOutput.directory); const htmlUri = Uri.file(projectGraphOutput.fullPath); let projectGraphHtml = (await workspace.fs.readFile(htmlUri)).toString(); projectGraphHtml = projectGraphHtml.replace( /static\//g, `${panel.webview.asWebviewUri(rootUri)}/static/`, ); projectGraphHtml = projectGraphHtml.replace( '</head>', html` <style> #sidebar { display: none; } div[data-cy="no-projects-selected"] { display: none } #no-projects-chosen { display: none } [data-cy="no-tasks-selected"] { display: none; } body { background-color: var(--vscode-settings-editor-background) !important; } .nx-select-project { padding: 12px; } </style> <script>${injectedScript()}</script> ${await setNxConsoleEnvironment()} </head>`, ); projectGraphHtml = projectGraphHtml.replace( '</body>', html` <script> ${registerFileClickListener()}; ${registerOpenProjectConfigCallback()}; ${registerRunTaskCallback()}; </script> </body>`, ); return projectGraphHtml; } function injectedScript() { return ` (function() { const vscode = acquireVsCodeApi(); window.vscode = vscode; const noProjectElement = document.createElement('p'); noProjectElement.classList.add('nx-select-project'); noProjectElement.innerText = "Please select or focus a Nx project"; vscode.postMessage({ command: 'ready', }) let previousSelectedProject = null; function waitForAndClickOnElement(data) { function clickOnElement() { if (window.externalApi) { let action = null; switch (data.type) { case '${MessageType.select}': { if (previousSelectedProject === data.projectName) { action = { type: 'deselectProject', projectName: data.projectName }; previousSelectedProject = null; } else { action = { type: 'selectProject', projectName: data.projectName }; previousSelectedProject = data.projectName; } break; } case '${MessageType.focus}': { action = { type: 'focusProject', projectName: data.projectName }; break; } case '${MessageType.affectedProjects}': { action = { type: 'affectedProjects'}; break; } case '${MessageType.task}': { action = { type: 'task', taskName: data.taskName, projectName: data.projectName }; break; } case '${MessageType.allTasks}': { action = { type: 'allTasks', taskName: data.taskName }; break; } case '${MessageType.all}': default: { action = { type: 'selectAll' }; } } if (action.type === 'task') { document.querySelector(\`[data-cy="deselectAllButton"]\`)?.click() window.externalApi.router?.navigate(\`/tasks/\${action.taskName}\`).then(() => { document.querySelector(\`label[data-project="\${action.projectName}"\`)?.click() }) return true; } else if (action.type === 'allTasks') { document.querySelector(\`[data-cy="deselectAllButton"]\`)?.click() window.externalApi.router?.navigate(\`/tasks/\${action.taskName}\`).then(() => { document.querySelector(\`[data-cy="selectAllButton"]\`)?.click() }) return true; } else if(action.type === 'affectedProjects') { window.externalApi.router?.navigate(\`/projects/affected\`); return true; } let service = null; if ('projectGraphService' in window.externalApi) { service = window.externalApi.projectGraphService; } if ('depGraphService' in window.externalApi) { service = window.externalApi.depGraphService; } setTimeout(() => { service.send(action); }) return true; } if (data.type === "${MessageType.all}") { const allProjectsElement = document.querySelector(\`[data-cy="selectAllButton"]\`); if (allProjectsElement) { setTimeout(() => allProjectsElement.click(), 0) return true; } else { return false; } } const projectElement = document.querySelector(\`[data-project="\${data.projectName}"]\`); if (projectElement) { if (data.type === "${MessageType.focus}") { projectElement.parentElement.querySelector('button').click(); } else if (data.type === "${MessageType.select}") { projectElement.click(); } return true; } // nx12 has a different structure for the project graph const projectCheckbox = document.querySelector(\`input[value="\${data.projectName}"]\`); if (projectCheckbox) { if (data.type === "${MessageType.focus}") { projectCheckbox.parentElement.parentElement.querySelector('button').click(); } else if (data.type === "${MessageType.select}") { projectCheckbox.click(); } return true; } return false; } function centerGraph() { window.externalApi?.graphService?.renderGraph?.cy?.fit() const zoom = window.externalApi?.graphService?.renderGraph?.cy?.zoom(); if(zoom) { window.externalApi?.graphService?.renderGraph?.cy?.zoom(zoom * 0.9); } window.externalApi?.graphService?.renderGraph?.cy?.center(); } if (clickOnElement()) { setTimeout(() => centerGraph(), 0); return; } const observer = new MutationObserver(mutations => { const success = clickOnElement(); if (success) { setTimeout(() => centerGraph(), 0); observer.disconnect(); } }); observer.observe(document.body, { childList: true, subtree: true }); } window.addEventListener('message', ({ data }) => { if (!data) { document.body.prepend(noProjectElement); return; } try { document.body.removeChild(noProjectElement); } catch (e) { //noop } setTimeout(() => waitForAndClickOnElement(data), 0); }) }()) `; } async function setNxConsoleEnvironment() { const nxVersion = await getNxVersion(); if (gte(nxVersion?.full ?? '0.0.0', '16.6.0')) { return '<script> window.environment = "nx-console"</script>'; } else { return ''; } } function registerFileClickListener() { return ` window.externalApi?.registerFileClickCallback?.((message) => { window.vscode.postMessage({ command: 'fileClick', data: message }) }) `; } function registerOpenProjectConfigCallback() { return ` window.externalApi?.registerOpenProjectConfigCallback?.((message) => { window.vscode.postMessage({ command: 'openProject', data: message }) }) `; } function registerRunTaskCallback() { return ` window.externalApi?.registerRunTaskCallback?.((message) => { window.vscode.postMessage({ command: 'runTask', data: message }) }) `; }

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