From f33d525799eeea2e36ad809889121ad81a6ba48d Mon Sep 17 00:00:00 2001 From: CD Cabrera Date: Thu, 27 Aug 2026 20:13:37 -0400 Subject: [PATCH 1/3] feat(collections): pf-4402 activate patternfly api --- .../__snapshots__/server.test.ts.snap | 33 +++++++++++++++++ src/options.registry.ts | 4 ++- src/patternFly.getResources.ts | 4 ++- .../__snapshots__/httpTransport.test.ts.snap | 31 ++++++++++++---- .../__snapshots__/stdioTransport.test.ts.snap | 35 +++++++++++++++---- 5 files changed, 93 insertions(+), 14 deletions(-) diff --git a/src/__tests__/__snapshots__/server.test.ts.snap b/src/__tests__/__snapshots__/server.test.ts.snap index a8a2a8db..e53d4ca0 100644 --- a/src/__tests__/__snapshots__/server.test.ts.snap +++ b/src/__tests__/__snapshots__/server.test.ts.snap @@ -21,6 +21,9 @@ exports[`runServer should allow server to be stopped, http stop server: diagnost [ "Registered collection: patternfly-component-schemas", ], + [ + "Registered collection: patternfly-api", + ], [ "Registered resource: patternfly-context", ], @@ -89,6 +92,9 @@ exports[`runServer should allow server to be stopped, stdio stop server: diagnos [ "Registered collection: patternfly-component-schemas", ], + [ + "Registered collection: patternfly-api", + ], [ "Registered resource: patternfly-context", ], @@ -157,6 +163,9 @@ exports[`runServer should attempt to run server, create transport, connect, and [ "Registered collection: patternfly-component-schemas", ], + [ + "Registered collection: patternfly-api", + ], [ "Registered resource: patternfly-context", ], @@ -236,6 +245,9 @@ exports[`runServer should attempt to run server, disable SIGINT handler: diagnos [ "Registered collection: patternfly-component-schemas", ], + [ + "Registered collection: patternfly-api", + ], [ "Registered resource: patternfly-context", ], @@ -310,6 +322,9 @@ exports[`runServer should attempt to run server, enable SIGINT handler explicitl [ "Registered collection: patternfly-component-schemas", ], + [ + "Registered collection: patternfly-api", + ], [ "Registered resource: patternfly-context", ], @@ -389,6 +404,9 @@ exports[`runServer should attempt to run server, log warnings for experimental o [ "Registered collection: patternfly-component-schemas", ], + [ + "Registered collection: patternfly-api", + ], [ "Registered resource: patternfly-context", ], @@ -483,6 +501,9 @@ exports[`runServer should attempt to run server, register a tool: diagnostics 1` [ "Registered collection: patternfly-component-schemas", ], + [ + "Registered collection: patternfly-api", + ], [ "Registered resource: patternfly-context", ], @@ -570,6 +591,9 @@ exports[`runServer should attempt to run server, register multiple tools: diagno [ "Registered collection: patternfly-component-schemas", ], + [ + "Registered collection: patternfly-api", + ], [ "Registered resource: patternfly-context", ], @@ -664,6 +688,9 @@ exports[`runServer should attempt to run server, use custom options: diagnostics [ "Registered collection: patternfly-component-schemas", ], + [ + "Registered collection: patternfly-api", + ], [ "Registered resource: patternfly-context", ], @@ -743,6 +770,9 @@ exports[`runServer should attempt to run server, use default tools, http: diagno [ "Registered collection: patternfly-component-schemas", ], + [ + "Registered collection: patternfly-api", + ], [ "Registered resource: patternfly-context", ], @@ -831,6 +861,9 @@ exports[`runServer should attempt to run server, use default tools, stdio: diagn [ "Registered collection: patternfly-component-schemas", ], + [ + "Registered collection: patternfly-api", + ], [ "Registered resource: patternfly-context", ], diff --git a/src/options.registry.ts b/src/options.registry.ts index cb87218e..c5103329 100644 --- a/src/options.registry.ts +++ b/src/options.registry.ts @@ -11,6 +11,7 @@ import { patternFlySchemasIndexResource } from './resource.patternFlySchemasInde import { patternFlySchemasTemplateResource } from './resource.patternFlySchemasTemplate'; import { patternFlyDocsCollection } from './collection.patternFlyDocs'; import { patternFlySchemasCollection } from './collection.patternFlySchemas'; +import { patternFlyApiCollection } from './collection.patternFlyApi'; /** * Built-in tools. @@ -44,7 +45,8 @@ const builtinResources: McpResourceCreator[] = [ */ const builtinCollections: McpCollectionCreator[] = [ patternFlyDocsCollection, - patternFlySchemasCollection + patternFlySchemasCollection, + patternFlyApiCollection ]; export { builtinCollections, builtinResources, builtinTools }; diff --git a/src/patternFly.getResources.ts b/src/patternFly.getResources.ts index 633ad7bc..6527f05b 100644 --- a/src/patternFly.getResources.ts +++ b/src/patternFly.getResources.ts @@ -492,10 +492,12 @@ const getPatternFlyMcpResources = async (contextPathOverride?: string): Promise< const { componentNamesIndex, byVersion: componentNamesByVersion, byDocs: componentNamesByDocs } = componentNames; const originalDocs = patternFlyRecordsRegistry.get('patternfly-docs'); + const apiCollection = patternFlyRecordsRegistry.get('patternfly-api'); const catalog = [ ...originalDocs?.records?.flatMap(({ data }) => Object.entries(data as Record)) || [], - ...Array.from(componentNamesByDocs) + ...Array.from(componentNamesByDocs), + ...apiCollection?.records?.flatMap(({ data }) => Object.entries(data as Record)) || [] ]; const resources = new Map(); diff --git a/tests/e2e/__snapshots__/httpTransport.test.ts.snap b/tests/e2e/__snapshots__/httpTransport.test.ts.snap index 126c1adb..10d038f3 100644 --- a/tests/e2e/__snapshots__/httpTransport.test.ts.snap +++ b/tests/e2e/__snapshots__/httpTransport.test.ts.snap @@ -10,7 +10,7 @@ Use these parameters to filter the list of PatternFly components. | Parameter | Valid Values | Description | | :--- | :--- | :--- | -| \`category\` | \`accessibility\`, \`design-guidelines\`, \`react\` | Filter by category | +| \`category\` | \`accessibility\`, \`css\`, \`design-guidelines\`, \`html\`, \`react\`, \`text\` | Filter by category | | \`version\` | \`v5\`, \`v6\` | Filter by version | ## Available Patterns @@ -31,8 +31,8 @@ Use these parameters to filter the PatternFly documentation index. | Parameter | Valid Values | Description | | :--- | :--- | :--- | -| \`category\` | \`accessibility\`, \`design-guidelines\`, \`design-tokens\`, \`development-guidelines\`, \`grammar\`, \`react\`, \`writing-guides\` | Filter by category | -| \`section\` | \`ai\`, \`charts\`, \`components\`, \`content-design\`, \`extensions\`, \`foundations-and-styles\`, \`get-started\`, \`guidelines\`, \`layouts\`, \`patterns\`, \`resources\`, \`upgrade\` | Filter by section | +| \`category\` | \`about\`, \`about-rapid-prototyping\`, \`accessibility\`, \`analytics\`, \`chat-bot\`, \`css\`, \`customizing%20messages\`, \`demo\`, \`demo-demos\`, \`design-guidelines\`, \`design-tokens\`, \`development-guide\`, \`development-guidelines\`, \`enhancing-existing-projects\`, \`general\`, \`grammar\`, \`html\`, \`new-prototypes\`, \`overview\`, \`react\`, \`release-notes\`, \`text\`, \`upgrade-guide\`, \`writing-guidelines\`, \`writing-guides\` | Filter by category | +| \`section\` | \`accessibility\`, \`ai\`, \`charts\`, \`components\`, \`content-design\`, \`developer-guides\`, \`extensions\`, \`foundations-and-styles\`, \`get-help\`, \`get-involved\`, \`get-started\`, \`guidelines\`, \`layouts\`, \`patterns\`, \`releases\`, \`resources\`, \`upgrade\` | Filter by section | | \`version\` | \`v4\`, \`v5\`, \`v6\` | Filter by version | ## Available Patterns @@ -58,7 +58,7 @@ Use these parameters to filter the list of PatternFly component schemas. | Parameter | Valid Values | Description | | :--- | :--- | :--- | -| \`category\` | \`accessibility\`, \`design-guidelines\`, \`react\` | Filter by category | +| \`category\` | \`accessibility\`, \`css\`, \`design-guidelines\`, \`html\`, \`react\`, \`text\` | Filter by category | | \`version\` | \`v5\`, \`v6\` | Filter by version | ## Available Patterns @@ -107,7 +107,7 @@ exports[`Builtin tools, HTTP transport should initialize MCP session over HTTP 1 `; exports[`Builtin tools, HTTP transport should return expected markdown structure for search results: markdown 1`] = ` -"# Search results for PatternFly version "v6" and "button". Showing 2 exact matches. +"# Search results for PatternFly version "v6" and "button". Showing 3 exact matches. 1. **button**: "usePatternFlyDocs" resource parameter "name" and "URLs" - **Name**: button @@ -115,11 +115,30 @@ exports[`Builtin tools, HTTP transport should return expected markdown structure - [Button - (v6) - Design Guidelines for the button component.](https://raw.githubusercontent.com/patternfly/patternfly-org/957756128e8ddfc4be5db49e72312a2c43b9d220/packages/documentation-site/patternfly-docs/content/components/button/button.md) - [Button - (v6) - Accessibility for the button component.](https://raw.githubusercontent.com/patternfly/patternfly-org/957756128e8ddfc4be5db49e72312a2c43b9d220/packages/documentation-site/patternfly-docs/content/components/accessibility/button/button.md) - [Button - (v6) - Examples for the button component.](https://raw.githubusercontent.com/patternfly/patternfly-react/6f2385bbcc03de22a7ae8be506481e1b4d653200/packages/react-core/src/components/Button/examples/Button.md) + - [Button CSS - (v6) - PatternFly variables and tokens for Button CSS.](https://main.patternfly-org.pages.dev/api/v6/components/button/css) + - [Button - (v6) - A favorite button should use a plain button with the star icon. Applying .pf-m-favorited to the button initiates a microanimation and indicates that the item is favorited.](https://main.patternfly-org.pages.dev/api/v6/components/button/html) + - [Button - (v6) - PatternFly examples and demos for Button.](https://main.patternfly-org.pages.dev/api/v6/components/button/react/examples/ButtonBlock) + - [Button - (v6) - PatternFly examples and demos for Button.](https://main.patternfly-org.pages.dev/api/v6/components/button/react/examples/ButtonCallToAction) + - [Button - (v6) - PatternFly examples and demos for Button.](https://main.patternfly-org.pages.dev/api/v6/components/button/react/examples/ButtonDisabled) + - [Button - (v6) - PatternFly examples and demos for Button.](https://main.patternfly-org.pages.dev/api/v6/components/button/react/examples/ButtonInlineSpanLink) + - [Button - (v6) - PatternFly examples and demos for Button.](https://main.patternfly-org.pages.dev/api/v6/components/button/react/examples/ButtonLinks) + - [Button - (v6) - PatternFly examples and demos for Button.](https://main.patternfly-org.pages.dev/api/v6/components/button/react/examples/ButtonProgress) + - [Button - (v6) - PatternFly examples and demos for Button.](https://main.patternfly-org.pages.dev/api/v6/components/button/react/examples/ButtonSmall) + - [Button - (v6) - PatternFly examples and demos for Button.](https://main.patternfly-org.pages.dev/api/v6/components/button/react/examples/ButtonVariations) + - [Button - (v6) - There are certain cases where specific buttons must be used within your UI.](https://main.patternfly-org.pages.dev/api/v6/components/button/text) - **Resources**: - **URI**: patternfly://docs/button?version=v6 - **JSON Schemas**: patternfly://schemas/button?version=v6 -2. **patterns**: +2. **extensionscomponentgroupsclosebutton**: + "usePatternFlyDocs" resource parameter "name" and "URLs" + - **Name**: extensionscomponentgroupsclosebutton + - **URLs**: + - [Component Groups Close Button - (v6) - The close button component provides a way for users to exit a modal, dialogue, or similar action. To further customize this component, you can also utilize all properties of the button component.](https://main.patternfly-org.pages.dev/api/v6/extensions/component-groups_close-button/react) + - **Resources**: + - **URI**: patternfly://docs/extensionscomponentgroupsclosebutton?version=v6 + +3. **patterns**: "usePatternFlyDocs" resource parameter "name" and "URLs" - **Name**: patterns - **URLs**: diff --git a/tests/e2e/__snapshots__/stdioTransport.test.ts.snap b/tests/e2e/__snapshots__/stdioTransport.test.ts.snap index 6e3f4718..8cdc67f2 100644 --- a/tests/e2e/__snapshots__/stdioTransport.test.ts.snap +++ b/tests/e2e/__snapshots__/stdioTransport.test.ts.snap @@ -10,7 +10,7 @@ Use these parameters to filter the list of PatternFly components. | Parameter | Valid Values | Description | | :--- | :--- | :--- | -| \`category\` | \`accessibility\`, \`design-guidelines\`, \`react\` | Filter by category | +| \`category\` | \`accessibility\`, \`css\`, \`design-guidelines\`, \`html\`, \`react\`, \`text\` | Filter by category | | \`version\` | \`v5\`, \`v6\` | Filter by version | ## Available Patterns @@ -31,8 +31,8 @@ Use these parameters to filter the PatternFly documentation index. | Parameter | Valid Values | Description | | :--- | :--- | :--- | -| \`category\` | \`accessibility\`, \`design-guidelines\`, \`design-tokens\`, \`development-guidelines\`, \`grammar\`, \`react\`, \`writing-guides\` | Filter by category | -| \`section\` | \`ai\`, \`charts\`, \`components\`, \`content-design\`, \`extensions\`, \`foundations-and-styles\`, \`get-started\`, \`guidelines\`, \`layouts\`, \`patterns\`, \`resources\`, \`upgrade\` | Filter by section | +| \`category\` | \`about\`, \`about-rapid-prototyping\`, \`accessibility\`, \`analytics\`, \`chat-bot\`, \`css\`, \`customizing%20messages\`, \`demo\`, \`demo-demos\`, \`design-guidelines\`, \`design-tokens\`, \`development-guide\`, \`development-guidelines\`, \`enhancing-existing-projects\`, \`general\`, \`grammar\`, \`html\`, \`new-prototypes\`, \`overview\`, \`react\`, \`release-notes\`, \`text\`, \`upgrade-guide\`, \`writing-guidelines\`, \`writing-guides\` | Filter by category | +| \`section\` | \`accessibility\`, \`ai\`, \`charts\`, \`components\`, \`content-design\`, \`developer-guides\`, \`extensions\`, \`foundations-and-styles\`, \`get-help\`, \`get-involved\`, \`get-started\`, \`guidelines\`, \`layouts\`, \`patterns\`, \`releases\`, \`resources\`, \`upgrade\` | Filter by section | | \`version\` | \`v4\`, \`v5\`, \`v6\` | Filter by version | ## Available Patterns @@ -58,7 +58,7 @@ Use these parameters to filter the list of PatternFly component schemas. | Parameter | Valid Values | Description | | :--- | :--- | :--- | -| \`category\` | \`accessibility\`, \`design-guidelines\`, \`react\` | Filter by category | +| \`category\` | \`accessibility\`, \`css\`, \`design-guidelines\`, \`html\`, \`react\`, \`text\` | Filter by category | | \`version\` | \`v5\`, \`v6\` | Filter by version | ## Available Patterns @@ -107,7 +107,7 @@ exports[`Builtin tools, STDIO should expose expected tools and stable shape 1`] `; exports[`Builtin tools, STDIO should return expected markdown structure for search results: markdown 1`] = ` -"# Search results for PatternFly version "v6" and "button". Showing 2 exact matches. +"# Search results for PatternFly version "v6" and "button". Showing 3 exact matches. 1. **button**: "usePatternFlyDocs" resource parameter "name" and "URLs" - **Name**: button @@ -115,11 +115,30 @@ exports[`Builtin tools, STDIO should return expected markdown structure for sear - [Button - (v6) - Design Guidelines for the button component.](https://raw.githubusercontent.com/patternfly/patternfly-org/957756128e8ddfc4be5db49e72312a2c43b9d220/packages/documentation-site/patternfly-docs/content/components/button/button.md) - [Button - (v6) - Accessibility for the button component.](https://raw.githubusercontent.com/patternfly/patternfly-org/957756128e8ddfc4be5db49e72312a2c43b9d220/packages/documentation-site/patternfly-docs/content/components/accessibility/button/button.md) - [Button - (v6) - Examples for the button component.](https://raw.githubusercontent.com/patternfly/patternfly-react/6f2385bbcc03de22a7ae8be506481e1b4d653200/packages/react-core/src/components/Button/examples/Button.md) + - [Button CSS - (v6) - PatternFly variables and tokens for Button CSS.](https://main.patternfly-org.pages.dev/api/v6/components/button/css) + - [Button - (v6) - A favorite button should use a plain button with the star icon. Applying .pf-m-favorited to the button initiates a microanimation and indicates that the item is favorited.](https://main.patternfly-org.pages.dev/api/v6/components/button/html) + - [Button - (v6) - PatternFly examples and demos for Button.](https://main.patternfly-org.pages.dev/api/v6/components/button/react/examples/ButtonBlock) + - [Button - (v6) - PatternFly examples and demos for Button.](https://main.patternfly-org.pages.dev/api/v6/components/button/react/examples/ButtonCallToAction) + - [Button - (v6) - PatternFly examples and demos for Button.](https://main.patternfly-org.pages.dev/api/v6/components/button/react/examples/ButtonDisabled) + - [Button - (v6) - PatternFly examples and demos for Button.](https://main.patternfly-org.pages.dev/api/v6/components/button/react/examples/ButtonInlineSpanLink) + - [Button - (v6) - PatternFly examples and demos for Button.](https://main.patternfly-org.pages.dev/api/v6/components/button/react/examples/ButtonLinks) + - [Button - (v6) - PatternFly examples and demos for Button.](https://main.patternfly-org.pages.dev/api/v6/components/button/react/examples/ButtonProgress) + - [Button - (v6) - PatternFly examples and demos for Button.](https://main.patternfly-org.pages.dev/api/v6/components/button/react/examples/ButtonSmall) + - [Button - (v6) - PatternFly examples and demos for Button.](https://main.patternfly-org.pages.dev/api/v6/components/button/react/examples/ButtonVariations) + - [Button - (v6) - There are certain cases where specific buttons must be used within your UI.](https://main.patternfly-org.pages.dev/api/v6/components/button/text) - **Resources**: - **URI**: patternfly://docs/button?version=v6 - **JSON Schemas**: patternfly://schemas/button?version=v6 -2. **patterns**: +2. **extensionscomponentgroupsclosebutton**: + "usePatternFlyDocs" resource parameter "name" and "URLs" + - **Name**: extensionscomponentgroupsclosebutton + - **URLs**: + - [Component Groups Close Button - (v6) - The close button component provides a way for users to exit a modal, dialogue, or similar action. To further customize this component, you can also utilize all properties of the button component.](https://main.patternfly-org.pages.dev/api/v6/extensions/component-groups_close-button/react) + - **Resources**: + - **URI**: patternfly://docs/extensionscomponentgroupsclosebutton?version=v6 + +3. **patterns**: "usePatternFlyDocs" resource parameter "name" and "URLs" - **Name**: patterns - **URLs**: @@ -159,6 +178,10 @@ exports[`Logging should allow setting logging options, stderr 1`] = ` "[INFO]: Registered collection: patternfly-docs ", "[INFO]: Registered collection: patternfly-component-schemas +", + "[INFO]: Registered collection: patternfly-api +", + "[INFO]: Update collection: patternfly-api ", "[INFO]: Update collection: patternfly-component-schemas ", From e6b72ba7d91376a38b4541087cc35bbd34325be6 Mon Sep 17 00:00:00 2001 From: CD Cabrera Date: Wed, 2 Sep 2026 10:33:18 -0400 Subject: [PATCH 2/3] fix: review update --- src/docs.filterWords.ts | 2 +- src/patternFly.search.ts | 23 ++++++++++++++++++++--- 2 files changed, 21 insertions(+), 4 deletions(-) diff --git a/src/docs.filterWords.ts b/src/docs.filterWords.ts index 1350d35d..f02b63b3 100644 --- a/src/docs.filterWords.ts +++ b/src/docs.filterWords.ts @@ -4,7 +4,7 @@ * @note It's tempting to remove category and section names from this list, don't. Instead, the search * should be leveraging filters which allow for "section" and "category" specifically. */ -const INDEX_BLOCKLIST_WORDS = ['patternfly', 'component', 'components', 'documentation', 'example', 'examples', 'view']; +const INDEX_BLOCKLIST_WORDS = ['patternfly', 'component', 'components', 'documentation', 'example', 'examples', 'type', 'types', 'view']; /** * Technical terms and acronyms that should be exempt from length and noise filtering. diff --git a/src/patternFly.search.ts b/src/patternFly.search.ts index 17f4c03c..6b73d8cd 100644 --- a/src/patternFly.search.ts +++ b/src/patternFly.search.ts @@ -192,6 +192,7 @@ const calculateRelevance = ( const normalizedName = normalizeString.memo(result.name); const normalizedQuery = normalizeString.memo(query); + // Exact match if (normalizedName === normalizedQuery) { return 0; } @@ -200,16 +201,32 @@ const calculateRelevance = ( .map(entry => (entry.displayName ? normalizeString.memo(entry.displayName) : '')) .filter(Boolean); + // Exact match if (displayNames.some(name => name === normalizedQuery)) { return 0; } - if (normalizedName.includes(normalizedQuery) || - displayNames.some(name => name.includes(normalizedQuery))) { + // Substring match + if ( + normalizedName.includes(normalizedQuery) || + normalizedQuery.includes(normalizedName) || + displayNames.some(name => name.includes(normalizedQuery) || normalizedQuery.includes(name)) + ) { return 1; } - return 2; + // Multi-word name match + const queryTokens = normalizedQuery.split(' ').filter(token => token.length > 2); + const hasTokenMatch = queryTokens.some(token => + normalizedName === token || + displayNames.some(name => name === token)); + + if (hasTokenMatch) { + return 2; + } + + // Everything else + return 3; }; /** From 76501ef7f43520817d8469ab5dded8fde8057502 Mon Sep 17 00:00:00 2001 From: CD Cabrera Date: Fri, 11 Sep 2026 16:56:24 -0400 Subject: [PATCH 3/3] fix: review update --- .../collection.patternFlyApi.test.ts.snap | 4 +- .../collection.patternFlyApi.test.ts | 4 +- .../collection.patternFlyApiHelpers.test.ts | 14 ++-- src/collection.patternFlyApi.ts | 2 +- src/collection.patternFlyApiHelpers.ts | 48 +++++++---- src/resource.helpers.ts | 79 ++++++++++++++++++- 6 files changed, 122 insertions(+), 29 deletions(-) diff --git a/src/__tests__/__snapshots__/collection.patternFlyApi.test.ts.snap b/src/__tests__/__snapshots__/collection.patternFlyApi.test.ts.snap index 1ac58ce9..0b6c1a48 100644 --- a/src/__tests__/__snapshots__/collection.patternFlyApi.test.ts.snap +++ b/src/__tests__/__snapshots__/collection.patternFlyApi.test.ts.snap @@ -5,7 +5,7 @@ exports[`collectionCallback should match snapshot for collection result 1`] = ` "records": [ { "data": { - "card": [ + "Card": [ { "category": "css", "contentType": "", @@ -46,7 +46,7 @@ exports[`getPatternFlyApiRecords should attempt to convert expanded embedded rec [ { "data": { - "dolor": [ + "Dolor": [ { "category": "react", "contentType": "text/markdown", diff --git a/src/__tests__/collection.patternFlyApi.test.ts b/src/__tests__/collection.patternFlyApi.test.ts index 9bee9be0..866f541c 100644 --- a/src/__tests__/collection.patternFlyApi.test.ts +++ b/src/__tests__/collection.patternFlyApi.test.ts @@ -234,7 +234,7 @@ describe('collectionCallback', () => { expect(keys.length).toBe(1); const key: any = keys[0]; - expect(key).toBe('button'); + expect(key).toBe('Button'); expect(first).toMatchObject({ sourceId: `${BASE}/v1/components/Button/react` @@ -284,7 +284,7 @@ describe('collectionCallback', () => { const key: any = rec?.data ? Object.keys(rec.data)[0] : ''; - expect(key).toBe('card'); + expect(key).toBe('Card'); expect(rec?.data?.[key]).toContainEqual(expect.objectContaining({ displayName: 'Card', category: 'overview' diff --git a/src/__tests__/collection.patternFlyApiHelpers.test.ts b/src/__tests__/collection.patternFlyApiHelpers.test.ts index 1671cd00..e3bb4194 100644 --- a/src/__tests__/collection.patternFlyApiHelpers.test.ts +++ b/src/__tests__/collection.patternFlyApiHelpers.test.ts @@ -966,43 +966,43 @@ describe('extractApiName', () => { description: 'components section returns normalized item name', item: 'Button', section: 'components', - expected: 'button' + expected: 'Button' }, { description: 'components section with uppercase and whitespace', item: ' Card ', section: ' Components ', - expected: 'card' + expected: 'Card' }, { description: 'overview item with custom section adds suffix', item: 'overview', section: 'utilities', - expected: 'utilities-overview' + expected: 'UtilitiesOverview' }, { description: 'item already prefixed with section avoids double prefix', item: 'charts-pie', section: 'charts', - expected: 'charts-pie' + expected: 'ChartsPie' }, { description: 'item already prefixed with uppercase section name', item: 'Patterns-Gallery', section: 'patterns', - expected: 'patterns-gallery' + expected: 'PatternsGallery' }, { description: 'non-prefixed item in custom section prefixes section', item: 'pie', section: 'charts', - expected: 'charts-pie' + expected: 'ChartsPie' }, { description: 'non-prefixed item in patterns section prefixes section', item: 'gallery', section: 'patterns', - expected: 'patterns-gallery' + expected: 'PatternsGallery' } ])('should extract API name, $description', ({ item, section, expected }) => { expect(extractApiName(item, section)).toBe(expected); diff --git a/src/collection.patternFlyApi.ts b/src/collection.patternFlyApi.ts index 0d300de2..fd69dc66 100644 --- a/src/collection.patternFlyApi.ts +++ b/src/collection.patternFlyApi.ts @@ -544,7 +544,7 @@ const contentMetadata = (record: ApiCrawler | ApiEmbeddedExpanded, options = get normalizedFacet, normalizedDetailType, normalizedDetail, - ...remaining.map(normalizeSlug) + ...remaining.map(remainder => normalizeSlug(remainder)) ].filter(Boolean).join('-'); const name = extractApiName(normalizedItem, normalizedSection); diff --git a/src/collection.patternFlyApiHelpers.ts b/src/collection.patternFlyApiHelpers.ts index 51c5aeef..31413b48 100644 --- a/src/collection.patternFlyApiHelpers.ts +++ b/src/collection.patternFlyApiHelpers.ts @@ -3,7 +3,9 @@ import { contentType, getInlinedCodeBlockCount, isJson, - isJsonLike + isJsonLike, + stringToCase, + DEFAULT_ACRONYMS } from './resource.helpers'; /** @@ -195,12 +197,14 @@ const calculateContentQualityScore = ( * Transform a string. * * @param segment - Input string to normalize. + * @param [settings] - Optional settings + * @param [settings.acronyms] - Acronyms to avoid * @returns Normalized slug. */ -const normalizeSlug = (segment: string): string => { +const normalizeSlug = (segment: string, { acronyms = DEFAULT_ACRONYMS }: { acronyms?: string[] } = {}): string => { let updatedSegment = segment; - if (/[A-Z]/.test(updatedSegment) && !/^(ai|css|html|mcp|cli|uxd|ui|api|faq|faqs|aria|rtl)$/i.test(updatedSegment)) { + if (/[A-Z]/.test(updatedSegment) && !new RegExp(`^(${acronyms.join('|')})$`, 'i').test(updatedSegment)) { const split = updatedSegment.split(/(?=[A-Z])/); if (split.every(val => /^[A-Z]/.test(val))) { @@ -221,13 +225,14 @@ const normalizeSlug = (segment: string): string => { * * @param slug * @param section + * @param [settings] - Optional settings + * @param [settings.acronyms] - Acronyms to avoid */ -const formatSlugToTitle = (slug: string, section?: string): string => { +const formatSlugToTitle = (slug: string, section?: string, { acronyms = DEFAULT_ACRONYMS } = {}): string => { if (!slug) { return 'PatternFly API'; } - const acronyms = ['ai', 'css', 'html', 'mcp', 'cli', 'uxd', 'ui', 'api', 'faq', 'faqs', 'aria', 'rtl']; const acronymRegex = new RegExp(`^(${acronyms.join('|')})$`, 'i'); const cleanSection = section @@ -268,13 +273,23 @@ const formatSlugToTitle = (slug: string, section?: string): string => { * @param [context.slug] - Optional slug used for fallback or secondary formatting of the display name. * @param [context.category] - Optional category of content being processed (e.g., 'props', 'css', or 'doc'). * @param [context.section] - Optional section name used for refining the display name. + * @param [context.detail] - Optional detail string used for refining the display name. + * @param [context.detailType] - Optional detail type used for refining the display name. * @returns Extracted or formatted display name for the API item. */ -const extractApiDisplayName = (content?: string, context: { slug?: string; category?: string; section?: string; } = {}): string => { - const { slug = '', category = 'doc', section } = context || {}; +const extractApiDisplayName = ( + content?: string, + context: { slug?: string; category?: string; section?: string; detail?: string; detailType?: string } = {} +): string => { + const { slug = '', category = 'doc', section, detail = '', detailType = '' } = context || {}; const trimmed = content?.trim() || ''; + // Use example detail + if (detailType === 'examples' && detail) { + return formatSlugToTitle(detail, section); + } + // Props JSON signature if (category === 'props' && trimmed.startsWith('{')) { try { @@ -453,23 +468,24 @@ const extractApiDescription = ( * @returns Extracted entry name */ const extractApiName = (item: string, section: string): string => { - const normalizedItem = item.trim().toLowerCase(); - const normalizedSection = section.trim().toLowerCase(); + const normalizedItem = item.trim(); + const normalizedSection = section.trim(); + let updatedName = `${normalizedSection}-${normalizedItem}`; - if (normalizedSection === 'components') { - return normalizedItem; + if (normalizedSection.toLowerCase() === 'components') { + updatedName = normalizedItem; } - if (normalizedItem === 'overview') { - return `${normalizedSection}-overview`; + if (normalizedItem.toLowerCase() === 'overview') { + updatedName = `${normalizedSection}-overview`; } // Prevent double-prefix - if (normalizedItem.startsWith(`${normalizedSection}-`)) { - return normalizedItem; + if (normalizedItem.toLowerCase().startsWith(`${normalizedSection.toLowerCase()}-`)) { + updatedName = normalizedItem; } - return `${normalizedSection}-${normalizedItem}`; + return stringToCase(updatedName, { type: 'pascal' }); }; export { diff --git a/src/resource.helpers.ts b/src/resource.helpers.ts index defa1e37..48bdfbc1 100644 --- a/src/resource.helpers.ts +++ b/src/resource.helpers.ts @@ -2,6 +2,80 @@ import { filterPatternFly, type FilterPatternFlyFilters } from './patternFly.sea import { normalizeEnumeratedPatternFlyVersion } from './patternFly.helpers'; import { isPlainObject } from './server.helpers'; +/** + * Common default regex to tokenize mixed string formats (kebab, snake, camel, spaces, punctuation). + */ +const DEFAULT_STRING_SPLIT_REGEX = /[\s\-_.:/]+|(?<=[a-z0-9])(?=[A-Z])|(?<=[A-Z])(?=[A-Z][a-z])/; + +/** + * Common acronyms to preserve across transformations. + */ +const DEFAULT_ACRONYMS = ['ai', 'css', 'html', 'mcp', 'cli', 'uxd', 'ui', 'api', 'faq', 'faqs', 'aria', 'rtl']; + +/** + * Convert a string into a casing style. + * + * @param str - Input to convert. + * @param [options] - Config options. + * @param [options.type] - Target case type for the output string. Defaults to `camel`. + * @param [options.splitRegex] - A regex or string used to split the input words. + * @param [options.acronyms] - Array of common acronyms. + * @returns Converted string or an empty string if the input is either invalid or contains zero words. + */ +const stringToCase = ( + str: unknown, + { + type = 'camel', + splitRegex = DEFAULT_STRING_SPLIT_REGEX, + acronyms = DEFAULT_ACRONYMS + }: { type?: 'snake' | 'pascal' | 'camel'; splitRegex?: RegExp | string; acronyms?: string[] } = {} +) => { + const words = typeof str === 'string' && str.trim().length + ? str + .trim() + .split(splitRegex) + .map(word => word.trim()) + .filter(Boolean) + : []; + + if (!words.length) { + return ''; + } + + const acronymRegex = acronyms.length > 0 ? new RegExp(`^(${acronyms.join('|')})$`, 'i') : null; + + switch (type) { + case 'snake': + return words.map(word => word.toLowerCase()).join('_'); + + case 'pascal': + return words + .map(word => { + if (acronymRegex?.test(word)) { + return word.toUpperCase(); + } + + return word.charAt(0).toUpperCase() + word.slice(1).toLowerCase(); + }) + .join(''); + + case 'camel': + default: + return words + .map((word, index) => { + if (index === 0) { + return word.toLowerCase(); + } + if (acronymRegex?.test(word)) { + return word.toUpperCase(); + } + + return word.charAt(0).toUpperCase() + word.slice(1).toLowerCase(); + }) + .join(''); + } +}; + /** * Count the number of inlined code blocks in a given string. * @@ -558,5 +632,8 @@ export { isScriptLike, isShellLike, isXmlLike, - paramCompletion + paramCompletion, + stringToCase, + DEFAULT_STRING_SPLIT_REGEX, + DEFAULT_ACRONYMS };