diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 88709e7d..4a336b79 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -170,25 +170,31 @@ npm run test:integration This mode leverages the `--mode test` and `--mode-test-url` flags to redirect resource lookups to a fixture server instead of live or local resources. -## Maintenance: Node.js engine bumps +## Maintenance + +### Node.js engine bumps The `Node.js` engine requirements are updated on a predictable biannual schedule to ensure the server remains secure, leverages modern runtime features, and provides stability for consumers. > Our engine requirements are intended to be the minimum to run the MCP server. They are not intended to be a maximum, as newer versions may introduce breaking changes or require additional configuration. -### Schedule and process +#### Schedule and process - **Timing**: Bumps are generally targeted for **Spring (April/May)** and **Fall (October/November)**, aligned with the [Node.js release schedule](https://nodejs.org/en/about/previous-releases) as versions enter or exit LTS. - **Security**: Out-of-band updates may be performed if critical security considerations arise. - **Version Targets**: - Focus on the latest **even-numbered (LTS/Stable)** versions (e.g., bumping to 22, 24, or 26). - GitHub Workflows should be updated to include the latest available even version. -### Acceptance criteria for bumps +#### Acceptance criteria for bumps - Update `package.json` engine requirements. - Update related GitHub Action workflows (CI/CD). - Update "Environmental Requirements" in documentation. - Ensure all tests pass on the new target version. +### Project maintenance + +For information on project development maintenance tasks, refer to [Project development maintenance in docs/development.md](./docs/development.md#project-development-maintenance). + ## AI agent ### User section diff --git a/docs/development.md b/docs/development.md index 23a17354..b350ee8f 100644 --- a/docs/development.md +++ b/docs/development.md @@ -8,7 +8,7 @@ Complete guide to using the PatternFly MCP Server for development including CLI - [Experimental settings](./experimental.md) - [MCP tool plugins](#mcp-tool-plugins) - [Initial troubleshooting](#initial-troubleshooting) -- [Project maintenance](#project-maintenance) +- [Project development maintenance](#project-development-maintenance) - [In-progress and future work](#in-progress-and-future-work) ## CLI usage @@ -446,9 +446,22 @@ These terms describe **how tools and their related properties are represented** - **Missing tools/resources**: Verify the server started successfully and check logs with `--log-stderr`. - **Type errors**: Ensure TypeScript types are installed: `npm install --save-dev @types/node` -## Project maintenance +## Project development maintenance -For information on how we manage project dependencies, including our biannual Node.js engine bump schedule, please refer to the [Maintenance section in CONTRIBUTING.md](../CONTRIBUTING.md#maintenance-nodejs-engine-bumps). +For information on build maintenance, refer to [Maintenance in CONTRIBUTING.md](../CONTRIBUTING.md#nodejs-engine-bumps). + +### Updating collections + +The server packages pre-built collections (such as `src/collection.patternFlyApi.json`) to provide quick MCP startups. + +To refresh and validate the embedded API collection: + +```bash +npm run build:collections +``` + +- **Execution**: Crawls live PatternFly API endpoints, filters quality records, updates `src/collection.patternFlyApi.json`, and executes collection-specific Jest validation tests (`jest --selectProjects collections`). +- **When to run**: When PatternFly publishes new component API releases or when updating metadata/quality filters. ## In-progress and future work 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__/__snapshots__/options.defaults.test.ts.snap b/src/__tests__/__snapshots__/options.defaults.test.ts.snap index 37a6d3b4..71a87269 100644 --- a/src/__tests__/__snapshots__/options.defaults.test.ts.snap +++ b/src/__tests__/__snapshots__/options.defaults.test.ts.snap @@ -57,10 +57,9 @@ exports[`options defaults should return specific properties: defaults 1`] = ` "props", "css", ], - "enabled": false, "schedule": { "continueOnError": true, - "delayStartMs": 21600000, + "delayStartMs": 604800000, "intervalMs": 604800000, "repeat": Infinity, }, 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/__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/__tests__/resource.helpers.test.ts b/src/__tests__/resource.helpers.test.ts index 9bdd0497..249d5140 100644 --- a/src/__tests__/resource.helpers.test.ts +++ b/src/__tests__/resource.helpers.test.ts @@ -13,6 +13,7 @@ import { isScriptLike, isShellLike, isXmlLike, + stringToCase, paramCompletion } from '../resource.helpers'; import { filterPatternFly } from '../patternFly.search'; @@ -567,6 +568,122 @@ describe('formatContentForMarkdown', () => { }); }); +describe('stringToCase', () => { + it.each([ + { + description: 'kebab-case to camelCase', + input: 'button-group-item', + options: undefined, + expected: 'buttonGroupItem' + }, + { + description: 'snake_case to camelCase', + input: 'button_group_item', + options: { type: 'camel' }, + expected: 'buttonGroupItem' + }, + { + description: 'PascalCase to camelCase', + input: 'ButtonGroupItem', + options: { type: 'camel' }, + expected: 'buttonGroupItem' + }, + { + description: 'mixed delimiters, colons and slashes', + input: 'components/button:react-guidelines', + options: { type: 'camel' }, + expected: 'componentsButtonReactGuidelines' + }, + { + description: 'keeps acronyms in camelCase, trailing', + input: 'button-css-api', + options: { type: 'camel' }, + expected: 'buttonCSSAPI' + }, + { + description: 'keeps acronyms in camelCase, leading', + input: 'mcp-server-ui', + options: { type: 'camel' }, + expected: 'mcpServerUI' + }, + { + description: 'kebab-case to PascalCase', + input: 'about-modal-box', + options: { type: 'pascal' }, + expected: 'AboutModalBox' + }, + { + description: 'keeps acronyms in PascalCase', + input: 'patternfly-mcp-api', + options: { type: 'pascal' }, + expected: 'PatternflyMCPAPI' + }, + { + description: 'PascalCase to snake_case', + input: 'AboutModalBox', + options: { type: 'snake' }, + expected: 'about_modal_box' + }, + { + description: 'kebab-case to snake_case', + input: 'button-group-item', + options: { type: 'snake' }, + expected: 'button_group_item' + }, + { + description: 'kebab-case to Title Case', + input: 'action-list-item', + options: { type: 'title' }, + expected: 'Action List Item' + }, + { + description: 'keeps acronyms in Title Case', + input: 'mcp-html-aria-rules', + options: { type: 'title' }, + expected: 'MCP HTML ARIA Rules' + }, + { + description: 'empty string', + input: '', + expected: '' + }, + { + description: 'whitespace only', + input: ' ', + expected: '' + }, + { + description: 'null input', + input: null, + expected: '' + }, + { + description: 'undefined input', + input: undefined, + expected: '' + }, + { + description: 'number input', + input: 12345, + expected: '' + }, + { + description: 'custom acronym array', + input: 'custom-foo-bar', + options: { type: 'pascal', acronyms: ['foo'] }, + expected: 'CustomFOOBar' + }, + { + description: 'custom split regex', + input: 'custom|pipe|separated', + options: { splitRegex: /\|/ }, + expected: 'customPipeSeparated' + } + ])('should convert string case, $description', ({ input, options, expected }) => { + expect(stringToCase(input, options as any)).toBe(expected); + }); +}); + describe('paramCompletion', () => { beforeEach(() => { jest.clearAllMocks(); diff --git a/src/collection.patternFlyApi.json b/src/collection.patternFlyApi.json index eeb9e111..0e655737 100644 --- a/src/collection.patternFlyApi.json +++ b/src/collection.patternFlyApi.json @@ -1,6 +1,6 @@ { "version": "1", - "generated": "2026-09-10T14:57:31.829Z", + "generated": "2026-09-11T21:17:36.541Z", "base": "https://main.patternfly-org.pages.dev/api", "records": [ { @@ -166,22 +166,22 @@ }, { "p": "v6/components/about-modal/react/examples/AboutModalBasic", - "n": "About Modal", - "d": "PatternFly examples and demos for About Modal.", + "n": "About Modal Basic", + "d": "PatternFly examples and demos for About Modal Basic.", "c": "javascript", "q": 1 }, { "p": "v6/components/about-modal/react/examples/AboutModalComplexUserPositionedContent", - "n": "About Modal", - "d": "PatternFly examples and demos for About Modal.", + "n": "About Modal Complex User Positioned Content", + "d": "PatternFly examples and demos for About Modal Complex User Positioned Content.", "c": "javascript", "q": 1 }, { "p": "v6/components/about-modal/react/examples/AboutModalWithoutProductName", - "n": "About Modal", - "d": "PatternFly examples and demos for About Modal.", + "n": "About Modal Without Product Name", + "d": "PatternFly examples and demos for About Modal Without Product Name.", "c": "javascript", "q": 1 }, @@ -208,36 +208,36 @@ }, { "p": "v6/components/accordion/react/examples/AccordionBordered", - "n": "Accordion", - "d": "PatternFly examples and demos for Accordion.", + "n": "Accordion Bordered", + "d": "PatternFly examples and demos for Accordion Bordered.", "c": "javascript", "q": 1 }, { "p": "v6/components/accordion/react/examples/AccordionDefinitionList", - "n": "Accordion", - "d": "PatternFly examples and demos for Accordion.", + "n": "Accordion Definition List", + "d": "PatternFly examples and demos for Accordion Definition List.", "c": "javascript", "q": 1 }, { "p": "v6/components/accordion/react/examples/AccordionFixedWithMultipleExpandBehavior", - "n": "Accordion", - "d": "PatternFly examples and demos for Accordion.", + "n": "Accordion Fixed With Multiple Expand Behavior", + "d": "PatternFly examples and demos for Accordion Fixed With Multiple Expand Behavior.", "c": "javascript", "q": 1 }, { "p": "v6/components/accordion/react/examples/AccordionSingleExpandBehavior", - "n": "Accordion", - "d": "PatternFly examples and demos for Accordion.", + "n": "Accordion Single Expand Behavior", + "d": "PatternFly examples and demos for Accordion Single Expand Behavior.", "c": "javascript", "q": 1 }, { "p": "v6/components/accordion/react/examples/AccordionToggleIconAtStart", - "n": "Accordion", - "d": "PatternFly examples and demos for Accordion.", + "n": "Accordion Toggle Icon At Start", + "d": "PatternFly examples and demos for Accordion Toggle Icon At Start.", "c": "javascript", "q": 1 }, @@ -264,29 +264,29 @@ }, { "p": "v6/components/action-list/react/examples/ActionListMultipleGroups", - "n": "Action List", - "d": "PatternFly examples and demos for Action List.", + "n": "Action List Multiple Groups", + "d": "PatternFly examples and demos for Action List Multiple Groups.", "c": "javascript", "q": 1 }, { "p": "v6/components/action-list/react/examples/ActionListSingleGroup", - "n": "Action List", - "d": "PatternFly examples and demos for Action List.", + "n": "Action List Single Group", + "d": "PatternFly examples and demos for Action List Single Group.", "c": "javascript", "q": 1 }, { "p": "v6/components/action-list/react/examples/ActionListWithCancelButton", - "n": "Action List", - "d": "PatternFly examples and demos for Action List.", + "n": "Action List With Cancel Button", + "d": "PatternFly examples and demos for Action List With Cancel Button.", "c": "javascript", "q": 1 }, { "p": "v6/components/action-list/react/examples/ActionListWithIcons", - "n": "Action List", - "d": "PatternFly examples and demos for Action List.", + "n": "Action List With Icons", + "d": "PatternFly examples and demos for Action List With Icons.", "c": "javascript", "q": 1 }, @@ -320,57 +320,57 @@ }, { "p": "v6/components/alert/react/examples/AlertAsyncLiveRegion", - "n": "Alert", - "d": "PatternFly examples and demos for Alert.", + "n": "Alert Async Live Region", + "d": "PatternFly examples and demos for Alert Async Live Region.", "c": "javascript", "q": 1 }, { "p": "v6/components/alert/react/examples/AlertDynamicLiveRegion", - "n": "Alert", - "d": "PatternFly examples and demos for Alert.", + "n": "Alert Dynamic Live Region", + "d": "PatternFly examples and demos for Alert Dynamic Live Region.", "c": "javascript", "q": 1 }, { "p": "v6/components/alert/react/examples/AlertGroupAsync", - "n": "Alert", - "d": "PatternFly examples and demos for Alert.", + "n": "Alert Group Async", + "d": "PatternFly examples and demos for Alert Group Async.", "c": "javascript", "q": 1 }, { "p": "v6/components/alert/react/examples/AlertGroupSingularDynamic", - "n": "Alert", - "d": "PatternFly examples and demos for Alert.", + "n": "Alert Group Singular Dynamic", + "d": "PatternFly examples and demos for Alert Group Singular Dynamic.", "c": "javascript", "q": 1 }, { "p": "v6/components/alert/react/examples/AlertGroupSingularDynamicOverflow", - "n": "Alert", - "d": "PatternFly examples and demos for Alert.", + "n": "Alert Group Singular Dynamic Overflow", + "d": "PatternFly examples and demos for Alert Group Singular Dynamic Overflow.", "c": "javascript", "q": 1 }, { "p": "v6/components/alert/react/examples/AlertGroupStatic", - "n": "Alert", - "d": "PatternFly examples and demos for Alert.", + "n": "Alert Group Static", + "d": "PatternFly examples and demos for Alert Group Static.", "c": "javascript", "q": 1 }, { "p": "v6/components/alert/react/examples/AlertGroupToast", - "n": "Alert", - "d": "PatternFly examples and demos for Alert.", + "n": "Alert Group Toast", + "d": "PatternFly examples and demos for Alert Group Toast.", "c": "javascript", "q": 1 }, { "p": "v6/components/alert/react/examples/AlertGroupToastOverflowCapture", - "n": "Alert", - "d": "PatternFly examples and demos for Alert.", + "n": "Alert Group Toast Overflow Capture", + "d": "PatternFly examples and demos for Alert Group Toast Overflow Capture.", "c": "javascript", "q": 1 }, @@ -397,36 +397,36 @@ }, { "p": "v6/components/avatar/react/examples/AvatarBasic", - "n": "Avatar", - "d": "PatternFly examples and demos for Avatar.", + "n": "Avatar Basic", + "d": "PatternFly examples and demos for Avatar Basic.", "c": "javascript", "q": 1 }, { "p": "v6/components/avatar/react/examples/AvatarBordered", - "n": "Avatar", - "d": "PatternFly examples and demos for Avatar.", + "n": "Avatar Bordered", + "d": "PatternFly examples and demos for Avatar Bordered.", "c": "javascript", "q": 1 }, { "p": "v6/components/avatar/react/examples/AvatarColorModifiers", - "n": "Avatar", - "d": "PatternFly examples and demos for Avatar.", + "n": "Avatar Color Modifiers", + "d": "PatternFly examples and demos for Avatar Color Modifiers.", "c": "javascript", "q": 1 }, { "p": "v6/components/avatar/react/examples/AvatarInitials", - "n": "Avatar", - "d": "PatternFly examples and demos for Avatar.", + "n": "Avatar Initials", + "d": "PatternFly examples and demos for Avatar Initials.", "c": "javascript", "q": 1 }, { "p": "v6/components/avatar/react/examples/AvatarSizeVariations", - "n": "Avatar", - "d": "PatternFly examples and demos for Avatar.", + "n": "Avatar Size Variations", + "d": "PatternFly examples and demos for Avatar Size Variations.", "c": "javascript", "q": 1 }, @@ -460,8 +460,8 @@ }, { "p": "v6/components/back-to-top/react/examples/BackToTopBasic", - "n": "Back To Top", - "d": "PatternFly examples and demos for Back To Top.", + "n": "Back To Top Basic", + "d": "PatternFly examples and demos for Back To Top Basic.", "c": "javascript", "q": 1 }, @@ -537,22 +537,22 @@ }, { "p": "v6/components/badge/react/examples/BadgeDisabled", - "n": "Badge", - "d": "PatternFly examples and demos for Badge.", + "n": "Badge Disabled", + "d": "PatternFly examples and demos for Badge Disabled.", "c": "javascript", "q": 1 }, { "p": "v6/components/badge/react/examples/BadgeRead", - "n": "Badge", - "d": "PatternFly examples and demos for Badge.", + "n": "Badge Read", + "d": "PatternFly examples and demos for Badge Read.", "c": "javascript", "q": 1 }, { "p": "v6/components/badge/react/examples/BadgeUnread", - "n": "Badge", - "d": "PatternFly examples and demos for Badge.", + "n": "Badge Unread", + "d": "PatternFly examples and demos for Badge Unread.", "c": "javascript", "q": 1 }, @@ -572,22 +572,22 @@ }, { "p": "v6/components/banner/react/examples/BannerBasic", - "n": "Banner", - "d": "PatternFly examples and demos for Banner.", + "n": "Banner Basic", + "d": "PatternFly examples and demos for Banner Basic.", "c": "javascript", "q": 1 }, { "p": "v6/components/banner/react/examples/BannerPill", - "n": "Banner", - "d": "PatternFly examples and demos for Banner.", + "n": "Banner Pill", + "d": "PatternFly examples and demos for Banner Pill.", "c": "javascript", "q": 1 }, { "p": "v6/components/banner/react/examples/BannerStatus", - "n": "Banner", - "d": "PatternFly examples and demos for Banner.", + "n": "Banner Status", + "d": "PatternFly examples and demos for Banner Status.", "c": "javascript", "q": 1 }, @@ -607,15 +607,15 @@ }, { "p": "v6/components/brand/react/examples/BrandBasic", - "n": "Brand", - "d": "PatternFly examples and demos for Brand.", + "n": "Brand Basic", + "d": "PatternFly examples and demos for Brand Basic.", "c": "javascript", "q": 1 }, { "p": "v6/components/brand/react/examples/BrandResponsive", - "n": "Brand", - "d": "PatternFly examples and demos for Brand.", + "n": "Brand Responsive", + "d": "PatternFly examples and demos for Brand Responsive.", "c": "javascript", "q": 1 }, @@ -642,29 +642,29 @@ }, { "p": "v6/components/breadcrumb/react/examples/BreadcrumbBasic", - "n": "Breadcrumb", - "d": "PatternFly examples and demos for Breadcrumb.", + "n": "Breadcrumb Basic", + "d": "PatternFly examples and demos for Breadcrumb Basic.", "c": "javascript", "q": 1 }, { "p": "v6/components/breadcrumb/react/examples/BreadcrumbDropdown", - "n": "Breadcrumb", - "d": "PatternFly examples and demos for Breadcrumb.", + "n": "Breadcrumb Dropdown", + "d": "PatternFly examples and demos for Breadcrumb Dropdown.", "c": "javascript", "q": 1 }, { "p": "v6/components/breadcrumb/react/examples/BreadcrumbWithHeading", - "n": "Breadcrumb", - "d": "PatternFly examples and demos for Breadcrumb.", + "n": "Breadcrumb With Heading", + "d": "PatternFly examples and demos for Breadcrumb With Heading.", "c": "javascript", "q": 1 }, { "p": "v6/components/breadcrumb/react/examples/BreadcrumbWithoutHomeLink", - "n": "Breadcrumb", - "d": "PatternFly examples and demos for Breadcrumb.", + "n": "Breadcrumb Without Home Link", + "d": "PatternFly examples and demos for Breadcrumb Without Home Link.", "c": "javascript", "q": 1 }, @@ -691,57 +691,57 @@ }, { "p": "v6/components/button/react/examples/ButtonBlock", - "n": "Button", - "d": "PatternFly examples and demos for Button.", + "n": "Button Block", + "d": "PatternFly examples and demos for Button Block.", "c": "javascript", "q": 1 }, { "p": "v6/components/button/react/examples/ButtonCallToAction", - "n": "Button", - "d": "PatternFly examples and demos for Button.", + "n": "Button Call To Action", + "d": "PatternFly examples and demos for Button Call To Action.", "c": "javascript", "q": 1 }, { "p": "v6/components/button/react/examples/ButtonDisabled", - "n": "Button", - "d": "PatternFly examples and demos for Button.", + "n": "Button Disabled", + "d": "PatternFly examples and demos for Button Disabled.", "c": "javascript", "q": 1 }, { "p": "v6/components/button/react/examples/ButtonInlineSpanLink", - "n": "Button", - "d": "PatternFly examples and demos for Button.", + "n": "Button Inline Span Link", + "d": "PatternFly examples and demos for Button Inline Span Link.", "c": "javascript", "q": 1 }, { "p": "v6/components/button/react/examples/ButtonLinks", - "n": "Button", - "d": "PatternFly examples and demos for Button.", + "n": "Button Links", + "d": "PatternFly examples and demos for Button Links.", "c": "javascript", "q": 1 }, { "p": "v6/components/button/react/examples/ButtonProgress", - "n": "Button", - "d": "PatternFly examples and demos for Button.", + "n": "Button Progress", + "d": "PatternFly examples and demos for Button Progress.", "c": "javascript", "q": 1 }, { "p": "v6/components/button/react/examples/ButtonSmall", - "n": "Button", - "d": "PatternFly examples and demos for Button.", + "n": "Button Small", + "d": "PatternFly examples and demos for Button Small.", "c": "javascript", "q": 1 }, { "p": "v6/components/button/react/examples/ButtonVariations", - "n": "Button", - "d": "PatternFly examples and demos for Button.", + "n": "Button Variations", + "d": "PatternFly examples and demos for Button Variations.", "c": "javascript", "q": 1 }, @@ -761,57 +761,57 @@ }, { "p": "v6/components/card/react/examples/CardBasic", - "n": "Card", - "d": "PatternFly examples and demos for Card.", + "n": "Card Basic", + "d": "PatternFly examples and demos for Card Basic.", "c": "javascript", "q": 1 }, { "p": "v6/components/card/react/examples/CardHeaderInCardHead", - "n": "Card", - "d": "PatternFly examples and demos for Card.", + "n": "Card Header In Card Head", + "d": "PatternFly examples and demos for Card Header In Card Head.", "c": "javascript", "q": 1 }, { "p": "v6/components/card/react/examples/CardHeaderWraps", - "n": "Card", - "d": "PatternFly examples and demos for Card.", + "n": "Card Header Wraps", + "d": "PatternFly examples and demos for Card Header Wraps.", "c": "javascript", "q": 1 }, { "p": "v6/components/card/react/examples/CardOnlyActionsInCardHead", - "n": "Card", - "d": "PatternFly examples and demos for Card.", + "n": "Card Only Actions In Card Head", + "d": "PatternFly examples and demos for Card Only Actions In Card Head.", "c": "javascript", "q": 1 }, { "p": "v6/components/card/react/examples/CardSecondary", - "n": "Card", - "d": "PatternFly examples and demos for Card.", + "n": "Card Secondary", + "d": "PatternFly examples and demos for Card Secondary.", "c": "javascript", "q": 1 }, { "p": "v6/components/card/react/examples/CardWithHeadingElement", - "n": "Card", - "d": "PatternFly examples and demos for Card.", + "n": "Card With Heading Element", + "d": "PatternFly examples and demos for Card With Heading Element.", "c": "javascript", "q": 1 }, { "p": "v6/components/card/react/examples/CardWithImageAndActions", - "n": "Card", - "d": "PatternFly examples and demos for Card.", + "n": "Card With Image And Actions", + "d": "PatternFly examples and demos for Card With Image And Actions.", "c": "javascript", "q": 1 }, { "p": "v6/components/card/react/examples/CardWithModifiers", - "n": "Card", - "d": "PatternFly examples and demos for Card.", + "n": "Card With Modifiers", + "d": "PatternFly examples and demos for Card With Modifiers.", "c": "javascript", "q": 1 }, @@ -950,8 +950,8 @@ }, { "p": "v6/developer-guides/react-flow/text/examples/CompassReactFlowDemo", - "n": "React Flow", - "d": "PatternFly examples and demos for React Flow.", + "n": "Compass React Flow Demo", + "d": "PatternFly examples and demos for Compass React Flow Demo.", "c": "javascript", "q": 1 }, @@ -1139,8 +1139,8 @@ }, { "p": "v6/foundations-and-styles/layouts_bullseye/react/examples/BullseyeBasic", - "n": "Layouts Bullseye", - "d": "PatternFly examples and demos for Layouts Bullseye.", + "n": "Bullseye Basic", + "d": "PatternFly examples and demos for Bullseye Basic.", "c": "javascript", "q": 1 }, @@ -1160,57 +1160,57 @@ }, { "p": "v6/foundations-and-styles/layouts_flex/react/examples/FlexBasic", - "n": "Layouts Flex", - "d": "PatternFly examples and demos for Layouts Flex.", + "n": "Flex Basic", + "d": "PatternFly examples and demos for Flex Basic.", "c": "javascript", "q": 1 }, { "p": "v6/foundations-and-styles/layouts_flex/react/examples/FlexColumnGap", - "n": "Layouts Flex", - "d": "PatternFly examples and demos for Layouts Flex.", + "n": "Flex Column Gap", + "d": "PatternFly examples and demos for Flex Column Gap.", "c": "javascript", "q": 1 }, { "p": "v6/foundations-and-styles/layouts_flex/react/examples/FlexIndividuallySpaced", - "n": "Layouts Flex", - "d": "PatternFly examples and demos for Layouts Flex.", + "n": "Flex Individually Spaced", + "d": "PatternFly examples and demos for Flex Individually Spaced.", "c": "javascript", "q": 1 }, { "p": "v6/foundations-and-styles/layouts_flex/react/examples/FlexNestedItems", - "n": "Layouts Flex", - "d": "PatternFly examples and demos for Layouts Flex.", + "n": "Flex Nested Items", + "d": "PatternFly examples and demos for Flex Nested Items.", "c": "javascript", "q": 1 }, { "p": "v6/foundations-and-styles/layouts_flex/react/examples/FlexNesting", - "n": "Layouts Flex", - "d": "PatternFly examples and demos for Layouts Flex.", + "n": "Flex Nesting", + "d": "PatternFly examples and demos for Flex Nesting.", "c": "javascript", "q": 1 }, { "p": "v6/foundations-and-styles/layouts_flex/react/examples/FlexRowGap", - "n": "Layouts Flex", - "d": "PatternFly examples and demos for Layouts Flex.", + "n": "Flex Row Gap", + "d": "PatternFly examples and demos for Flex Row Gap.", "c": "javascript", "q": 1 }, { "p": "v6/foundations-and-styles/layouts_flex/react/examples/FlexSpacingNone", - "n": "Layouts Flex", - "d": "PatternFly examples and demos for Layouts Flex.", + "n": "Flex Spacing None", + "d": "PatternFly examples and demos for Flex Spacing None.", "c": "javascript", "q": 1 }, { "p": "v6/foundations-and-styles/layouts_flex/react/examples/FlexSpacingXl", - "n": "Layouts Flex", - "d": "PatternFly examples and demos for Layouts Flex.", + "n": "Flex Spacing Xl", + "d": "PatternFly examples and demos for Flex Spacing Xl.", "c": "javascript", "q": 1 }, @@ -1230,43 +1230,43 @@ }, { "p": "v6/foundations-and-styles/layouts_gallery/react/examples/GalleryAdjustingMaxWidths", - "n": "Layouts Gallery", - "d": "PatternFly examples and demos for Layouts Gallery.", + "n": "Gallery Adjusting Max Widths", + "d": "PatternFly examples and demos for Gallery Adjusting Max Widths.", "c": "javascript", "q": 1 }, { "p": "v6/foundations-and-styles/layouts_gallery/react/examples/GalleryAdjustingMinMaxWidths", - "n": "Layouts Gallery", - "d": "PatternFly examples and demos for Layouts Gallery.", + "n": "Gallery Adjusting Min Max Widths", + "d": "PatternFly examples and demos for Gallery Adjusting Min Max Widths.", "c": "javascript", "q": 1 }, { "p": "v6/foundations-and-styles/layouts_gallery/react/examples/GalleryAdjustingMinWidths", - "n": "Layouts Gallery", - "d": "PatternFly examples and demos for Layouts Gallery.", + "n": "Gallery Adjusting Min Widths", + "d": "PatternFly examples and demos for Gallery Adjusting Min Widths.", "c": "javascript", "q": 1 }, { "p": "v6/foundations-and-styles/layouts_gallery/react/examples/GalleryAlternativeComponents", - "n": "Layouts Gallery", - "d": "PatternFly examples and demos for Layouts Gallery.", + "n": "Gallery Alternative Components", + "d": "PatternFly examples and demos for Gallery Alternative Components.", "c": "javascript", "q": 1 }, { "p": "v6/foundations-and-styles/layouts_gallery/react/examples/GalleryBasic", - "n": "Layouts Gallery", - "d": "PatternFly examples and demos for Layouts Gallery.", + "n": "Gallery Basic", + "d": "PatternFly examples and demos for Gallery Basic.", "c": "javascript", "q": 1 }, { "p": "v6/foundations-and-styles/layouts_gallery/react/examples/GalleryWithGutters", - "n": "Layouts Gallery", - "d": "PatternFly examples and demos for Layouts Gallery.", + "n": "Gallery With Gutters", + "d": "PatternFly examples and demos for Gallery With Gutters.", "c": "javascript", "q": 1 }, @@ -1286,50 +1286,50 @@ }, { "p": "v6/foundations-and-styles/layouts_grid/react/examples/GridAlternativeComponents", - "n": "Layouts Grid", - "d": "PatternFly examples and demos for Layouts Grid.", + "n": "Grid Alternative Components", + "d": "PatternFly examples and demos for Grid Alternative Components.", "c": "javascript", "q": 1 }, { "p": "v6/foundations-and-styles/layouts_grid/react/examples/GridBasic", - "n": "Layouts Grid", - "d": "PatternFly examples and demos for Layouts Grid.", + "n": "Grid Basic", + "d": "PatternFly examples and demos for Grid Basic.", "c": "javascript", "q": 1 }, { "p": "v6/foundations-and-styles/layouts_grid/react/examples/GridGroupingOrdering", - "n": "Layouts Grid", - "d": "PatternFly examples and demos for Layouts Grid.", + "n": "Grid Grouping Ordering", + "d": "PatternFly examples and demos for Grid Grouping Ordering.", "c": "javascript", "q": 1 }, { "p": "v6/foundations-and-styles/layouts_grid/react/examples/GridResponsiveOrdering", - "n": "Layouts Grid", - "d": "PatternFly examples and demos for Layouts Grid.", + "n": "Grid Responsive Ordering", + "d": "PatternFly examples and demos for Grid Responsive Ordering.", "c": "javascript", "q": 1 }, { "p": "v6/foundations-and-styles/layouts_grid/react/examples/GridStandardOrdering", - "n": "Layouts Grid", - "d": "PatternFly examples and demos for Layouts Grid.", + "n": "Grid Standard Ordering", + "d": "PatternFly examples and demos for Grid Standard Ordering.", "c": "javascript", "q": 1 }, { "p": "v6/foundations-and-styles/layouts_grid/react/examples/GridWithGutters", - "n": "Layouts Grid", - "d": "PatternFly examples and demos for Layouts Grid.", + "n": "Grid With Gutters", + "d": "PatternFly examples and demos for Grid With Gutters.", "c": "javascript", "q": 1 }, { "p": "v6/foundations-and-styles/layouts_grid/react/examples/GridWithOverrides", - "n": "Layouts Grid", - "d": "PatternFly examples and demos for Layouts Grid.", + "n": "Grid With Overrides", + "d": "PatternFly examples and demos for Grid With Overrides.", "c": "javascript", "q": 1 }, @@ -1349,15 +1349,15 @@ }, { "p": "v6/foundations-and-styles/layouts_level/react/examples/LevelBasic", - "n": "Layouts Level", - "d": "PatternFly examples and demos for Layouts Level.", + "n": "Level Basic", + "d": "PatternFly examples and demos for Level Basic.", "c": "javascript", "q": 1 }, { "p": "v6/foundations-and-styles/layouts_level/react/examples/LevelWithGutters", - "n": "Layouts Level", - "d": "PatternFly examples and demos for Layouts Level.", + "n": "Level With Gutters", + "d": "PatternFly examples and demos for Level With Gutters.", "c": "javascript", "q": 1 }, @@ -1377,22 +1377,22 @@ }, { "p": "v6/foundations-and-styles/layouts_split/react/examples/SplitBasic", - "n": "Layouts Split", - "d": "PatternFly examples and demos for Layouts Split.", + "n": "Split Basic", + "d": "PatternFly examples and demos for Split Basic.", "c": "javascript", "q": 1 }, { "p": "v6/foundations-and-styles/layouts_split/react/examples/SplitWithGutter", - "n": "Layouts Split", - "d": "PatternFly examples and demos for Layouts Split.", + "n": "Split With Gutter", + "d": "PatternFly examples and demos for Split With Gutter.", "c": "javascript", "q": 1 }, { "p": "v6/foundations-and-styles/layouts_split/react/examples/SplitWrappable", - "n": "Layouts Split", - "d": "PatternFly examples and demos for Layouts Split.", + "n": "Split Wrappable", + "d": "PatternFly examples and demos for Split Wrappable.", "c": "javascript", "q": 1 }, @@ -1412,15 +1412,15 @@ }, { "p": "v6/foundations-and-styles/layouts_stack/react/examples/StackBasic", - "n": "Layouts Stack", - "d": "PatternFly examples and demos for Layouts Stack.", + "n": "Stack Basic", + "d": "PatternFly examples and demos for Stack Basic.", "c": "javascript", "q": 1 }, { "p": "v6/foundations-and-styles/layouts_stack/react/examples/StackWithGutter", - "n": "Layouts Stack", - "d": "PatternFly examples and demos for Layouts Stack.", + "n": "Stack With Gutter", + "d": "PatternFly examples and demos for Stack With Gutter.", "c": "javascript", "q": 1 }, diff --git a/src/collection.patternFlyApi.ts b/src/collection.patternFlyApi.ts index 0d300de2..8152417d 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); @@ -552,7 +552,7 @@ const contentMetadata = (record: ApiCrawler | ApiEmbeddedExpanded, options = get const id = `api::${normalizedVersion}::${normalizedSection}::${normalizedItem}::${normalizedCategory}${normalizedDetailType ? `::${normalizedDetailType}::${normalizedDetail}` : ''}`; const displayName = (record as ApiEmbeddedExpanded)?.displayName || - extractApiDisplayName(content, { slug: normalizedItem, category: normalizedCategory, section: normalizedSection }); + extractApiDisplayName(content, { slug: normalizedItem, category: normalizedCategory, section: normalizedSection, detail: rawDetail, detailType: normalizedDetailType }); const description = (record as ApiEmbeddedExpanded)?.description || extractApiDescription(content, { displayName, category: normalizedCategory, detailType: normalizedDetailType }); diff --git a/src/collection.patternFlyApiHelpers.ts b/src/collection.patternFlyApiHelpers.ts index 51c5aeef..3987809f 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))) { @@ -219,6 +223,9 @@ const normalizeSlug = (segment: string): string => { * Format a compound slug into a clean title. * E.g., 'ai-assisted-development_ai-assisted-code-migration' -> 'AI Assisted Development: AI Assisted Code Migration' * + * @note The content in the API response slugs varies from snake to hyphens + * making it unique to parse. + * * @param slug * @param section */ @@ -227,36 +234,15 @@ const formatSlugToTitle = (slug: string, section?: string): string => { 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 - ? section - .split('-') - .map(wordPhrase => - (acronymRegex.test(wordPhrase) - ? wordPhrase.toUpperCase() - : wordPhrase.charAt(0).toUpperCase() + wordPhrase.slice(1))).join(' ') - : ''; + const cleanSection = section ? stringToCase(section, { type: 'title' }) : ''; - // Handle bare generic names like 'overview' if (slug.toLowerCase() === 'overview' && cleanSection) { return `${cleanSection} Overview`; } return slug .split('_') - .map(segment => - segment - .split('-') - .map(word => { - if (acronymRegex.test(word)) { - return word.toUpperCase(); - } - - return word.charAt(0).toUpperCase() + word.slice(1); - }) - .join(' ')) + .map(segment => stringToCase(segment, { type: 'title' })) .join(': '); }; @@ -268,13 +254,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 +449,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/options.defaults.ts b/src/options.defaults.ts index 582b82b8..15fae3b3 100644 --- a/src/options.defaults.ts +++ b/src/options.defaults.ts @@ -208,7 +208,6 @@ interface PatternFlyOptions { componentPaths: string[]; traversalPaths: string[]; timeoutMs: number; - enabled: boolean; schedule: { continueOnError: boolean; intervalMs: number; @@ -526,8 +525,8 @@ const CHANNEL_BASENAME = 'pf-mcp'; * the API grows. * - `schedule.intervalMs` is set to `7` days. Most users, without persistence, will * never achieve this. - * - `schedule.delayStartMs` is set to `6` hours to accommodate an intense working - * session. This may need to be extended until persistence is implemented. + * - `schedule.delayStartMs` AFTER persistence is set up will be `6` hours. Short term + * we've set it `7` days to align with the crawl interval. */ const PATTERNFLY_OPTIONS: PatternFlyOptions = { api: { @@ -544,10 +543,9 @@ const PATTERNFLY_OPTIONS: PatternFlyOptions = { schedule: { continueOnError: true, intervalMs: 24 * 60 * 60 * 1000 * 7, // 7 days - delayStartMs: 6 * 60 * 60 * 1000, // 6 hours + delayStartMs: 24 * 60 * 60 * 1000 * 7, // 7 days repeat: Infinity - }, - enabled: false // ToDo: confirm this is still used + } }, availableResourceVersions: ['6.0.0'], availableSearchVersions: ['current', 'latest', 'v6'], 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 0585546e..973c9167 100644 --- a/src/patternFly.getResources.ts +++ b/src/patternFly.getResources.ts @@ -494,10 +494,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/src/resource.helpers.ts b/src/resource.helpers.ts index defa1e37..514345a6 100644 --- a/src/resource.helpers.ts +++ b/src/resource.helpers.ts @@ -2,6 +2,91 @@ 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' | 'title' | '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 'title': + 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 +643,8 @@ export { isScriptLike, isShellLike, isXmlLike, - paramCompletion + paramCompletion, + stringToCase, + DEFAULT_STRING_SPLIT_REGEX, + DEFAULT_ACRONYMS }; diff --git a/tests/e2e/__snapshots__/httpTransport.test.ts.snap b/tests/e2e/__snapshots__/httpTransport.test.ts.snap index 4c86a54f..df27355c 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 1 exact match. +"# Search results for PatternFly version "v6" and "button". Showing 2 exact matches. 1. **button**: "usePatternFlyDocs" resource parameter "name" and "URLs" - **Name**: button @@ -115,10 +115,29 @@ 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 Block - (v6) - PatternFly examples and demos for Button Block.](https://main.patternfly-org.pages.dev/api/v6/components/button/react/examples/ButtonBlock) + - [Button Call To Action - (v6) - PatternFly examples and demos for Button Call To Action.](https://main.patternfly-org.pages.dev/api/v6/components/button/react/examples/ButtonCallToAction) + - [Button Disabled - (v6) - PatternFly examples and demos for Button Disabled.](https://main.patternfly-org.pages.dev/api/v6/components/button/react/examples/ButtonDisabled) + - [Button Inline Span Link - (v6) - PatternFly examples and demos for Button Inline Span Link.](https://main.patternfly-org.pages.dev/api/v6/components/button/react/examples/ButtonInlineSpanLink) + - [Button Links - (v6) - PatternFly examples and demos for Button Links.](https://main.patternfly-org.pages.dev/api/v6/components/button/react/examples/ButtonLinks) + - [Button Progress - (v6) - PatternFly examples and demos for Button Progress.](https://main.patternfly-org.pages.dev/api/v6/components/button/react/examples/ButtonProgress) + - [Button Small - (v6) - PatternFly examples and demos for Button Small.](https://main.patternfly-org.pages.dev/api/v6/components/button/react/examples/ButtonSmall) + - [Button Variations - (v6) - PatternFly examples and demos for Button Variations.](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. **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 + --- diff --git a/tests/e2e/__snapshots__/stdioTransport.test.ts.snap b/tests/e2e/__snapshots__/stdioTransport.test.ts.snap index 5399a28c..0d082784 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 1 exact match. +"# Search results for PatternFly version "v6" and "button". Showing 2 exact matches. 1. **button**: "usePatternFlyDocs" resource parameter "name" and "URLs" - **Name**: button @@ -115,10 +115,29 @@ 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 Block - (v6) - PatternFly examples and demos for Button Block.](https://main.patternfly-org.pages.dev/api/v6/components/button/react/examples/ButtonBlock) + - [Button Call To Action - (v6) - PatternFly examples and demos for Button Call To Action.](https://main.patternfly-org.pages.dev/api/v6/components/button/react/examples/ButtonCallToAction) + - [Button Disabled - (v6) - PatternFly examples and demos for Button Disabled.](https://main.patternfly-org.pages.dev/api/v6/components/button/react/examples/ButtonDisabled) + - [Button Inline Span Link - (v6) - PatternFly examples and demos for Button Inline Span Link.](https://main.patternfly-org.pages.dev/api/v6/components/button/react/examples/ButtonInlineSpanLink) + - [Button Links - (v6) - PatternFly examples and demos for Button Links.](https://main.patternfly-org.pages.dev/api/v6/components/button/react/examples/ButtonLinks) + - [Button Progress - (v6) - PatternFly examples and demos for Button Progress.](https://main.patternfly-org.pages.dev/api/v6/components/button/react/examples/ButtonProgress) + - [Button Small - (v6) - PatternFly examples and demos for Button Small.](https://main.patternfly-org.pages.dev/api/v6/components/button/react/examples/ButtonSmall) + - [Button Variations - (v6) - PatternFly examples and demos for Button Variations.](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. **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 + --- @@ -144,6 +163,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 ",