Skip to main content
Glama
nrwl

Nx MCP Server

Official
by nrwl
dependency-versioning.ts8.06 kB
import { gte } from '@nx-console/nx-version'; import { httpRequest } from '@nx-console/shared-utils'; import { rcompare } from 'semver'; import { QuickPickItem, QuickPickItemKind, window } from 'vscode'; import { join } from 'path'; import { getNxWorkspacePath } from '@nx-console/vscode-configuration'; import * as fs from 'fs'; type VersionMap = Record<string, { latest: string; all: string[] }>; export async function resolveDependencyVersioning( depInput: string, ): Promise<{ dep: string; version: string | undefined } | undefined> { const match = depInput.match(/^(.+)@(.+)/); if (match) { const [_, dep, version] = match; return { dep, version }; } // Special handling for Nx packages if (depInput.startsWith('@nx/')) { try { const nxVersion = await getNxVersionFromPackageJson(); if (nxVersion) { const options: QuickPickItem[] = [ { label: nxVersion, description: "matches 'nx' package", }, { label: 'Choose another version', description: '', }, ]; const selection = await window.showQuickPick(options, { placeHolder: `Select version for ${depInput}`, }); if (!selection) { return undefined; } if (selection.label === nxVersion) { return { dep: depInput, version: nxVersion }; } } } catch (e) { console.error('Error finding Nx version:', e); } } // Get package info and show the full version picker const packageInfo = await getPackageInfo(depInput); const versionMap = createVersionMap(packageInfo); const versionQuickPickOptions = createVersionQuickPickItems( packageInfo, versionMap, ); const version = await promptForVersion(versionQuickPickOptions, versionMap); if (!version) { return undefined; } return { dep: depInput, version }; } async function getNxVersionFromPackageJson(): Promise<string | undefined> { const workspacePath = getNxWorkspacePath(); if (!workspacePath) { return undefined; } const packageJsonPath = join(workspacePath, 'package.json'); try { const packageJsonContent = fs.readFileSync(packageJsonPath, 'utf8'); const packageJson = JSON.parse(packageJsonContent); // Check for 'nx' in dependencies or devDependencies const nxVersion = (packageJson.dependencies && packageJson.dependencies.nx) || (packageJson.devDependencies && packageJson.devDependencies.nx); // Skip if it's a canary version if (nxVersion && nxVersion.includes('canary')) { return undefined; } return nxVersion; } catch (e) { console.error('Error reading package.json:', e); return undefined; } } async function promptForVersion( versionQuickPickItems: QuickPickItem[], versionMap: VersionMap, ): Promise<string | undefined> { const selection = await new Promise<string | undefined>((resolve) => { const quickPick = window.createQuickPick(); quickPick.canSelectMany = false; quickPick.items = versionQuickPickItems; quickPick.onDidChangeValue(() => { quickPick.items = [ ...versionQuickPickItems, { label: quickPick.value, description: 'install specific version', }, ]; }); quickPick.onDidAccept(() => { resolve(quickPick.selectedItems[0]?.label); quickPick.hide(); quickPick.dispose(); }); quickPick.show(); }); const match = selection?.match(/^(\d+).x/); if (match) { const majorToSelect = match[1]; const version = await window.showQuickPick(versionMap[majorToSelect].all, { canPickMany: false, }); if (!version) { return promptForVersion(versionQuickPickItems, versionMap); } return version; } return selection; } /** * Create a map that tracks the latest version and an array of all versions per major version */ export function createVersionMap( packageInfo: PackageInformationResponse, ): VersionMap { const versionMap: VersionMap = {}; Object.entries(packageInfo.versions).forEach(([versionNum, versionInfo]) => { // Skip deprecated versions or canary versions if (versionInfo.deprecated || versionNum.includes('canary')) { return; } const major = versionNum.split('.')[0]; if (major === '0') { return; } if (!versionMap[major]) { versionMap[major] = { latest: versionNum, all: [] }; } versionMap[major].all.push(versionNum); if (gte(versionNum, versionMap[major].latest)) { versionMap[major].latest = versionNum; } }); return versionMap; } /** * For each major version, add options to the quickpick based on the updated requirements: * - next version if it exists * - latest version if it exists * - highest version if it's different from latest * - highest version one minor behind the highest * - option to select a specific version */ function createVersionQuickPickItems( packageInfo: PackageInformationResponse, versionMap: VersionMap, ): QuickPickItem[] { // Get next and latest tags const nextTag = packageInfo['dist-tags']?.next; const latestTag = packageInfo['dist-tags']?.latest; // Skip tags if they are canary versions const validNextTag = nextTag && !nextTag.includes('canary') ? nextTag : null; const validLatestTag = latestTag && !latestTag.includes('canary') ? latestTag : null; return Object.entries(versionMap) .sort( ( a: [keyof VersionMap, VersionMap[keyof VersionMap]], b: [keyof VersionMap, VersionMap[keyof VersionMap]], ) => (parseInt(a[0]) < parseInt(b[0]) ? 1 : -1), ) .flatMap(([major, { latest, all }], index) => { const allSorted = all.sort(rcompare); const quickPickOptions = []; quickPickOptions.push({ label: `Version ${major}.x`, kind: QuickPickItemKind.Separator, }); // Add 'next' version if it exists and belongs to this major if (validNextTag && validNextTag.startsWith(`${major}.`)) { quickPickOptions.push({ label: validNextTag, description: 'next', }); } // Add 'latest' version if it exists and belongs to this major if (validLatestTag && validLatestTag.startsWith(`${major}.`)) { quickPickOptions.push({ label: validLatestTag, description: 'latest', }); } // Add the highest version if it's not already covered by next/latest const highestVersion = allSorted[0]; if ( highestVersion !== validNextTag && highestVersion !== validLatestTag ) { quickPickOptions.push({ label: highestVersion, description: '', }); } // Add the version that's one minor behind the highest const minorBefore = allSorted.find( (v) => v.split('.')[1] === (parseInt(highestVersion.split('.')[1]) - 1).toString(), ); if ( minorBefore && minorBefore !== validNextTag && minorBefore !== validLatestTag ) { quickPickOptions.push({ label: minorBefore, description: '', }); } // Add option to select specific version if (allSorted.length > 1) { quickPickOptions.push({ label: `${major}.x`, description: 'select specific version', }); } return quickPickOptions; }); } export type PackageInformationResponse = { versions: Record<string, { deprecated?: string }>; 'dist-tags': Record<string, string>; }; export function getPackageInfo( dep: string, ): Promise<PackageInformationResponse> { const headers = { Accept: 'application/vnd.npm.install-v1+json', }; // https://github.com/npm/registry/blob/master/docs/responses/package-metadata.md return httpRequest({ url: `https://registry.npmjs.org/${dep}`, headers, }).then( (res) => JSON.parse(res.responseText), (error) => Promise.reject(error), ); }

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