Skip to main content
Glama
nrwl

Nx MCP Server

Official
by nrwl
normalize-schema.ts6.54 kB
import { CliOption, ItemsWithEnum, ItemTooltips, LongFormXPrompt, Option, OptionItemLabelValue, OptionPropertyDescription, XPrompt, } from './schema'; import type { Schema } from 'nx/src/utils/params'; export interface GeneratorDefaults { [name: string]: string; } export async function normalizeSchema( s: Schema, projectDefaults?: GeneratorDefaults ): Promise<Option[]> { const options = schemaToOptions(s); const requiredFields = new Set(s.required || []); const nxOptions = options.map((option) => { const xPrompt: XPrompt | undefined = option['x-prompt']; const workspaceDefault = projectDefaults?.[option.originalName ?? option.name]; const $default = option.$default; const nxOption: Option = { ...option, isRequired: isFieldRequired(requiredFields, option), aliases: option.alias ? [option.alias] : [], ...(workspaceDefault !== undefined && { default: workspaceDefault }), ...($default && { $default }), ...(option.enum && { items: option.enum.map((item) => item.toString()) }), // Strongly suspect items does not belong in the Option schema. // Angular Option doesn't have the items property outside of x-prompt, // but items is used in @schematics/angular - guard ...getItems(option), }; if (xPrompt) { nxOption.tooltip = isLongFormXPrompt(xPrompt) ? xPrompt.message : xPrompt; nxOption.itemTooltips = getEnumTooltips(xPrompt); if (isLongFormXPrompt(xPrompt) && !nxOption.items) { const items = (xPrompt.items || []).map((item) => isOptionItemLabelValue(item) ? typeof item.value === 'string' ? item.value : JSON.stringify(item.value) : item ); if (items.length > 0) { nxOption.items = items; } } } return nxOption; }); // since some folks are using Nx Console with older versions, // we need to make sure their options are sorted like before const optionComparator = nxOptions.some( (option) => option['x-priority'] !== undefined ) ? compareOptions : legacyCompareOptions; return nxOptions.sort(optionComparator); } /** * sorts options in the following order * - required * - x-priority: important * - everything else * - x-priority: internal * - deprecated * if two options are equal, they are sorted by whether they are positional args and name */ function compareOptions(a: Option, b: Option): number { function getPrio(opt: Option): number { if (opt.isRequired) { return 0; } if (opt['x-priority'] === 'important') { return 1; } if (opt['x-deprecated']) { return 4; } if (opt['x-priority'] === 'internal') { return 3; } return 2; } const aPrio = getPrio(a); const bPrio = getPrio(b); if (aPrio === bPrio) { if (typeof a.positional === 'number' && typeof b.positional === 'number') { return a.positional - b.positional; } if (typeof a.positional === 'number') { return -1; } else if (typeof b.positional === 'number') { return 1; } return a.name.localeCompare(b.name); } return aPrio - bPrio; } function legacyCompareOptions(a: Option, b: Option): number { const IMPORTANT_FIELD_NAMES = [ 'name', 'project', 'module', 'watch', 'style', 'directory', 'port', ]; const IMPORTANT_FIELDS_SET = new Set(IMPORTANT_FIELD_NAMES); if (typeof a.positional === 'number' && typeof b.positional === 'number') { return a.positional - b.positional; } if (typeof a.positional === 'number') { return -1; } else if (typeof b.positional === 'number') { return 1; } else if (a.isRequired) { if (b.isRequired) { return a.name.localeCompare(b.name); } return -1; } else if (b.isRequired) { return 1; } else if (IMPORTANT_FIELDS_SET.has(a.name)) { if (IMPORTANT_FIELDS_SET.has(b.name)) { return ( IMPORTANT_FIELD_NAMES.indexOf(a.name) - IMPORTANT_FIELD_NAMES.indexOf(b.name) ); } return -1; } else if (IMPORTANT_FIELDS_SET.has(b.name)) { return 1; } else { return a.name.localeCompare(b.name); } } function isFieldRequired( requiredFields: Set<string>, nxOption: CliOption ): boolean { // checks schema.json requiredFields and xPrompt for required return requiredFields.has(nxOption.name); } function getItems(option: CliOption): { items: string[] } | undefined { return ( option.items && { items: (option.items as ItemsWithEnum).enum || ((option.items as string[]).length && option.items), } ); } function isLongFormXPrompt(xPrompt: XPrompt): xPrompt is LongFormXPrompt { return (xPrompt as Partial<LongFormXPrompt>).message !== undefined; } function getEnumTooltips(xPrompt: XPrompt): ItemTooltips { const enumTooltips: ItemTooltips = {}; if (!!xPrompt && isLongFormXPrompt(xPrompt)) { (xPrompt.items || []).forEach((item) => { if (isOptionItemLabelValue(item) && !!item.label) { enumTooltips[item.value] = item.label; } }); } return enumTooltips; } function isOptionItemLabelValue( item: string | OptionItemLabelValue ): item is OptionItemLabelValue { return ( (item as Partial<OptionItemLabelValue>).value !== undefined || (item as Partial<OptionItemLabelValue>).label !== undefined ); } function schemaToOptions(schema: Schema): CliOption[] { return Object.keys(schema.properties || {}).reduce<CliOption[]>( (cliOptions, option) => { const currentProperty = schema.properties[option]; const $default = currentProperty.$default; const $defaultIndex = $default?.['$source'] === 'argv' ? $default['index'] : undefined; const positional: number | undefined = typeof $defaultIndex === 'number' ? $defaultIndex : undefined; const visible = isPropertyVisible(option, currentProperty); if (!visible) { return cliOptions; } cliOptions.push({ name: option, originalName: option, positional, ...currentProperty, }); return cliOptions; }, [] ); } function isPropertyVisible( option: string, property: OptionPropertyDescription ): boolean { const ALWAYS_VISIBLE_OPTIONS = ['path']; if (ALWAYS_VISIBLE_OPTIONS.includes(option)) { return true; } if ('hidden' in property) { return !(property as any)['hidden']; } return property.visible ?? true; }

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