diff --git a/cli/__tests__/convertToMDX.test.ts b/cli/__tests__/convertToMDX.test.ts index c96d309..1fb9e21 100644 --- a/cli/__tests__/convertToMDX.test.ts +++ b/cli/__tests__/convertToMDX.test.ts @@ -1,11 +1,10 @@ -import { readFile, writeFile, unlink } from 'fs/promises' +import { readFile, writeFile } from 'fs/promises' import { glob } from 'glob' import { convertToMDX } from '../convertToMDX.ts' jest.mock('fs/promises', () => ({ readFile: jest.fn(), writeFile: jest.fn(), - unlink: jest.fn(), access: jest.fn().mockResolvedValue(undefined), // Mock access to always resolve (file exists) })) @@ -132,17 +131,6 @@ import Example1 from './Example1.html?raw' expect(writeFile).toHaveBeenCalledWith('test.mdx', expectedContent) }) -it('should delete the original file after conversion', async () => { - const mockContent = '# Test Content\nSome text here' - ;(glob as unknown as jest.Mock).mockResolvedValue(['test.md']) - ;(readFile as jest.Mock).mockResolvedValue(mockContent) - - await convertToMDX('test.md') - - expect(writeFile).toHaveBeenCalledWith('test.mdx', mockContent) - expect(unlink).toHaveBeenCalledWith('test.md') -}) - it('should convert HTML comments in MD content to MDX format', async () => { const mockContent = '# Test Content\n\nSome text here\n' const expectedContent = '# Test Content\n{/* This is a comment in the MD content */}\nSome text here\n{/* Another comment\nspanning multiple lines */}' diff --git a/cli/convertToMDX.ts b/cli/convertToMDX.ts index 425249c..bdd0163 100644 --- a/cli/convertToMDX.ts +++ b/cli/convertToMDX.ts @@ -1,4 +1,4 @@ -import { readFile, writeFile, unlink, access } from 'fs/promises' +import { readFile, writeFile, access } from 'fs/promises' import { glob } from 'glob' import path from 'path' @@ -89,7 +89,6 @@ async function processFile(file: string): Promise { ) await writeFile(file + 'x', processedContent) - await unlink(file) } export async function convertToMDX(globPath: string): Promise { diff --git a/package-lock.json b/package-lock.json index 3dbf23e..aed5636 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,6 +16,7 @@ "@nanostores/react": "^0.8.4", "@patternfly/ast-helpers": "1.4.0-alpha.190", "@patternfly/patternfly": "^6.0.0", + "@patternfly/quickstarts": "^6.0.0", "@patternfly/react-code-editor": "^6.2.2", "@patternfly/react-core": "^6.0.0", "@patternfly/react-drag-drop": "^6.0.0", @@ -1268,6 +1269,7 @@ "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz", "integrity": "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==", "dev": true, + "license": "MIT", "optional": true, "engines": { "node": ">=0.1.90" @@ -3921,6 +3923,22 @@ "integrity": "sha512-FR027W7JygcQpvlRU/Iom936Vm0apzfi2o5lvtlcWW6IaeZCCTtTaDxehoYuELHlemzkLziQAgu6LuCJEVayjw==", "license": "MIT" }, + "node_modules/@patternfly/quickstarts": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/@patternfly/quickstarts/-/quickstarts-6.3.1.tgz", + "integrity": "sha512-cuQ+m0K90vbGyNo4oR8UToXo1Jw24QDfCaIoAW0pbUkEcYuSPGqVvrOSf7w5hUMJ8jrXqE7g0T7JkcQXElMbHg==", + "license": "MIT", + "dependencies": { + "dompurify": "^3.2.4", + "history": "^5.0.0" + }, + "peerDependencies": { + "@patternfly/react-core": "^6.0.0", + "marked": "^15.0.6", + "react": "^17 || ^18 || ^19", + "react-dom": "^17 || ^18 || ^19" + } + }, "node_modules/@patternfly/react-code-editor": { "version": "6.2.2", "resolved": "https://registry.npmjs.org/@patternfly/react-code-editor/-/react-code-editor-6.2.2.tgz", @@ -4808,6 +4826,7 @@ "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.6.0.tgz", "integrity": "sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==", "dev": true, + "license": "MIT", "engines": { "node": ">=10" }, @@ -5379,6 +5398,13 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/trusted-types": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz", + "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==", + "license": "MIT", + "optional": true + }, "node_modules/@types/unist": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", @@ -6424,7 +6450,8 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/anymatch": { "version": "3.1.3", @@ -7455,9 +7482,10 @@ } }, "node_modules/chalk": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", - "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==", + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", + "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", + "license": "MIT", "engines": { "node": "^12.17.0 || ^14.13 || >=16.0.0" }, @@ -7608,6 +7636,7 @@ "resolved": "https://registry.npmjs.org/cli-highlight/-/cli-highlight-2.1.11.tgz", "integrity": "sha512-9KDcoEVwyUXrjcJNvHD0NFc/hiwe/WPVYIleQh2O1N2Zro5gWJZ/K+3DGn8w8P/F6FxOgzyC5bxDyHIgCSPhGg==", "dev": true, + "license": "ISC", "dependencies": { "chalk": "^4.0.0", "highlight.js": "^10.7.1", @@ -7629,6 +7658,7 @@ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" } @@ -7638,6 +7668,7 @@ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, + "license": "MIT", "dependencies": { "color-convert": "^2.0.1" }, @@ -7653,6 +7684,7 @@ "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, + "license": "MIT", "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -7669,6 +7701,7 @@ "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", "dev": true, + "license": "ISC", "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.0", @@ -7679,19 +7712,22 @@ "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/cli-highlight/node_modules/parse5": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/parse5/-/parse5-5.1.1.tgz", "integrity": "sha512-ugq4DFI0Ptb+WWjAdOK16+u/nHfiIrcE+sh8kZMaM0WllQKLI9rOUq6c2b7cwPkXdzfQESqvoqK6ug7U/Yyzug==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/cli-highlight/node_modules/string-width": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", "dev": true, + "license": "MIT", "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", @@ -7706,6 +7742,7 @@ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "dev": true, + "license": "MIT", "dependencies": { "ansi-regex": "^5.0.1" }, @@ -7718,6 +7755,7 @@ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", "dev": true, + "license": "MIT", "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", @@ -7735,6 +7773,7 @@ "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", "dev": true, + "license": "MIT", "dependencies": { "cliui": "^7.0.2", "escalade": "^3.1.1", @@ -7753,6 +7792,7 @@ "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", "dev": true, + "license": "ISC", "engines": { "node": ">=10" } @@ -7775,6 +7815,7 @@ "resolved": "https://registry.npmjs.org/cli-table3/-/cli-table3-0.6.5.tgz", "integrity": "sha512-+W/5efTR7y5HRD7gACw9yQjqMVvEMLBHmboM/kPWam+H+Hmyrgjh6YncVKK122YZkXrLudzTuAukUw9FnMf7IQ==", "dev": true, + "license": "MIT", "dependencies": { "string-width": "^4.2.0" }, @@ -7790,6 +7831,7 @@ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" } @@ -7798,13 +7840,15 @@ "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/cli-table3/node_modules/string-width": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", "dev": true, + "license": "MIT", "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", @@ -7819,6 +7863,7 @@ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "dev": true, + "license": "MIT", "dependencies": { "ansi-regex": "^5.0.1" }, @@ -8958,6 +9003,15 @@ "node": ">=12" } }, + "node_modules/dompurify": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.2.7.tgz", + "integrity": "sha512-WhL/YuveyGXJaerVlMYGWhvQswa7myDG17P7Vu65EWC05o8vfeNbvNf4d/BOvH99+ZW+LlQsc1GDKMa1vNK6dw==", + "license": "(MPL-2.0 OR Apache-2.0)", + "optionalDependencies": { + "@types/trusted-types": "^2.0.7" + } + }, "node_modules/dot-prop": { "version": "5.3.0", "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.3.0.tgz", @@ -9074,7 +9128,8 @@ "version": "2.4.0", "resolved": "https://registry.npmjs.org/emojilib/-/emojilib-2.4.0.tgz", "integrity": "sha512-5U0rVMU5Y2n2+ykNLQqMoqklN9ICBT/KsvC1Gz6vqHbz2AXXGkG+Pm5rMWk/8Vjrr/mY9985Hi8DYzn1F09Nyw==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/encodeurl": { "version": "2.0.0", @@ -9245,6 +9300,7 @@ "resolved": "https://registry.npmjs.org/environment/-/environment-1.1.0.tgz", "integrity": "sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==", "dev": true, + "license": "MIT", "engines": { "node": ">=18" }, @@ -11646,10 +11702,20 @@ "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-10.7.3.tgz", "integrity": "sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==", "dev": true, + "license": "BSD-3-Clause", "engines": { "node": "*" } }, + "node_modules/history": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/history/-/history-5.3.0.tgz", + "integrity": "sha512-ZqaKwjjrAYUYfLG+htGaIIZ4nioX2L70ZUMIFysS3xvBsSG4x/n1V6TXV3N8ZYNuFGlDirFg32T7B6WOUPDYcQ==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.7.6" + } + }, "node_modules/homedir-polyfill": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/homedir-polyfill/-/homedir-polyfill-1.0.3.tgz", @@ -15246,10 +15312,11 @@ } }, "node_modules/marked": { - "version": "12.0.2", - "resolved": "https://registry.npmjs.org/marked/-/marked-12.0.2.tgz", - "integrity": "sha512-qXUm7e/YKFoqFPYPa3Ukg9xlI5cyAtGmyEIzMfW//m6kXwCy2Ps9DYf5ioijFKQ8qyuscrHoY04iJGctu2Kg0Q==", - "dev": true, + "version": "15.0.12", + "resolved": "https://registry.npmjs.org/marked/-/marked-15.0.12.tgz", + "integrity": "sha512-8dD6FusOQSrpv9Z1rdNMdlSgQOIP880DHqnohobOmYLElGEqAL/JvxvuxZO16r4HtjTlfPRDC1hbvxC9dPN2nA==", + "license": "MIT", + "peer": true, "bin": { "marked": "bin/marked.js" }, @@ -15258,31 +15325,33 @@ } }, "node_modules/marked-terminal": { - "version": "7.2.1", - "resolved": "https://registry.npmjs.org/marked-terminal/-/marked-terminal-7.2.1.tgz", - "integrity": "sha512-rQ1MoMFXZICWNsKMiiHwP/Z+92PLKskTPXj+e7uwXmuMPkNn7iTqC+IvDekVm1MPeC9wYQeLxeFaOvudRR/XbQ==", + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/marked-terminal/-/marked-terminal-7.3.0.tgz", + "integrity": "sha512-t4rBvPsHc57uE/2nJOLmMbZCQ4tgAccAED3ngXQqW6g+TxA488JzJ+FK3lQkzBQOI1mRV/r/Kq+1ZlJ4D0owQw==", "dev": true, + "license": "MIT", "dependencies": { "ansi-escapes": "^7.0.0", "ansi-regex": "^6.1.0", - "chalk": "^5.3.0", + "chalk": "^5.4.1", "cli-highlight": "^2.1.11", "cli-table3": "^0.6.5", - "node-emoji": "^2.1.3", + "node-emoji": "^2.2.0", "supports-hyperlinks": "^3.1.0" }, "engines": { "node": ">=16.0.0" }, "peerDependencies": { - "marked": ">=1 <15" + "marked": ">=1 <16" } }, "node_modules/marked-terminal/node_modules/ansi-escapes": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-7.0.0.tgz", - "integrity": "sha512-GdYO7a61mR0fOlAsvC9/rIHf7L96sBc6dEWzeOu+KAea5bZyQRPIpojrVoI4AXGJS/ycu/fBTdLrUkA4ODrvjw==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-7.1.1.tgz", + "integrity": "sha512-Zhl0ErHcSRUaVfGUeUdDuLgpkEo8KIFjB4Y9uAc46ScOpdDiU1Dbyplh7qWJeJ/ZHpbyMSM26+X3BySgnIz40Q==", "dev": true, + "license": "MIT", "dependencies": { "environment": "^1.0.0" }, @@ -16640,6 +16709,7 @@ "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", "dev": true, + "license": "MIT", "dependencies": { "any-promise": "^1.0.0", "object-assign": "^4.0.1", @@ -16731,6 +16801,7 @@ "resolved": "https://registry.npmjs.org/node-emoji/-/node-emoji-2.2.0.tgz", "integrity": "sha512-Z3lTE9pLaJF47NyMhd4ww1yFTAP8YhYI8SleJiHzM46Fgpm5cnNzSl9XfzFNqbaz+VlJrIj3fXQ4DeN1Rjm6cw==", "dev": true, + "license": "MIT", "dependencies": { "@sindresorhus/is": "^4.6.0", "char-regex": "^1.0.2", @@ -20359,6 +20430,7 @@ "resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-6.0.1.tgz", "integrity": "sha512-qPuWvbLgvDGilKc5BoicRovlT4MtYT6JfJyBOMDsKoiT+GiuP5qyrPCnR9HcPECIJJmZh5jRndyNThnhhb/vlA==", "dev": true, + "license": "MIT", "dependencies": { "parse5": "^6.0.1" } @@ -20367,7 +20439,8 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz", "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/path-browserify": { "version": "1.0.1", @@ -21977,6 +22050,19 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/semantic-release/node_modules/marked": { + "version": "12.0.2", + "resolved": "https://registry.npmjs.org/marked/-/marked-12.0.2.tgz", + "integrity": "sha512-qXUm7e/YKFoqFPYPa3Ukg9xlI5cyAtGmyEIzMfW//m6kXwCy2Ps9DYf5ioijFKQ8qyuscrHoY04iJGctu2Kg0Q==", + "dev": true, + "license": "MIT", + "bin": { + "marked": "bin/marked.js" + }, + "engines": { + "node": ">= 18" + } + }, "node_modules/semantic-release/node_modules/npm-run-path": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-6.0.0.tgz", @@ -22386,6 +22472,7 @@ "resolved": "https://registry.npmjs.org/skin-tone/-/skin-tone-2.0.0.tgz", "integrity": "sha512-kUMbT1oBJCpgrnKoSr0o6wPtvRWT9W9UKvGLwfJYO2WuahZRHOpEyL1ckyMGgMWh0UdpmaoFqKKD29WTomNEGA==", "dev": true, + "license": "MIT", "dependencies": { "unicode-emoji-modifier-base": "^1.0.0" }, @@ -22909,10 +22996,11 @@ } }, "node_modules/supports-hyperlinks": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-3.1.0.tgz", - "integrity": "sha512-2rn0BZ+/f7puLOHZm1HOJfwBggfaHXUpPUSSG/SWM4TWp5KCfmNYwnC3hruy2rZlMnmWZ+QAGpZfchu3f3695A==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-3.2.0.tgz", + "integrity": "sha512-zFObLMyZeEwzAoKCyu1B91U79K2t7ApXuQfo8OuxwXLDgcKxuwM+YvcbIhm6QWqz7mHUH1TVytR1PwVVjEuMig==", "dev": true, + "license": "MIT", "dependencies": { "has-flag": "^4.0.0", "supports-color": "^7.0.0" @@ -22921,7 +23009,7 @@ "node": ">=14.18" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/chalk/supports-hyperlinks?sponsor=1" } }, "node_modules/supports-preserve-symlinks-flag": { @@ -23059,6 +23147,7 @@ "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", "dev": true, + "license": "MIT", "dependencies": { "any-promise": "^1.0.0" } @@ -23068,6 +23157,7 @@ "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==", "dev": true, + "license": "MIT", "dependencies": { "thenify": ">= 3.1.0 < 4" }, @@ -23725,6 +23815,7 @@ "resolved": "https://registry.npmjs.org/unicode-emoji-modifier-base/-/unicode-emoji-modifier-base-1.0.0.tgz", "integrity": "sha512-yLSH4py7oFH3oG/9K+XWrz1pSi3dfUrWEnInbxMfArOfc1+33BlGPQtLsOYwvdMy11AwUBetYuaRxSPqgkq+8g==", "dev": true, + "license": "MIT", "engines": { "node": ">=4" } diff --git a/package.json b/package.json index 4a5ad59..6e9a8f7 100644 --- a/package.json +++ b/package.json @@ -60,6 +60,7 @@ "@patternfly/react-styles": "^6.0.0", "@patternfly/react-table": "^6.0.0", "@patternfly/react-tokens": "^6.0.0", + "@patternfly/quickstarts": "^6.0.0", "@types/react": "^18.3.23", "@types/react-dom": "^18.3.7", "astro": "5.13.3", diff --git a/pf-docs.config.mjs b/pf-docs.config.mjs index 9e60e72..9027c40 100644 --- a/pf-docs.config.mjs +++ b/pf-docs.config.mjs @@ -15,6 +15,8 @@ export const config = { pattern: "**/components/**/*.md", name: "react-component-docs", }, + { packageName: "@patternfly/patternfly", pattern: "*/**/*.md", name: "core-docs" }, + { packageName: "@patternfly/quickstarts", pattern: "*/patternfly-docs/**/*.md", name: "quickstarts-docs" }, ], outputDir: './dist', propsGlobs: [ diff --git a/src/components/Navigation.astro b/src/components/Navigation.astro index 82ca35c..ddfb742 100644 --- a/src/components/Navigation.astro +++ b/src/components/Navigation.astro @@ -41,20 +41,17 @@ const navData = sortedSections.map((section) => { .filter((entry) => entry.data.section === section) .map(entry => ({ id: entry.id, data: { id: entry.data.id, section, sortValue: entry.data.sortValue }} as TextContentEntry)) - let uniqueEntries = entries - if (section === 'components' || section === 'layouts') { - // only display unique entry.data.id in the nav list if the section is components - uniqueEntries = [ - ...entries - .reduce((map, entry) => { - if (!map.has(entry.data.id)) { - map.set(entry.data.id, entry) - } - return map - }, new Map()) - .values(), - ] - } + const uniqueEntries = [ + ...entries + .reduce((map, entry) => { + if (!map.has(entry.data.id)) { + map.set(entry.data.id, entry) + } + return map + }, new Map()) + .values(), + ] + // Sort alphabetically, unless a sort value is specified in the frontmatter const sortedUniqueEntries = uniqueEntries.sort((a, b) => { diff --git a/src/content.config.ts b/src/content.config.ts index 35b6068..6f8d9a7 100644 --- a/src/content.config.ts +++ b/src/content.config.ts @@ -33,6 +33,8 @@ function defineContent(contentObj: CollectionDefinition) { title: z.string().optional(), propComponents: z.array(z.string()).optional(), tab: z.string().optional().default(tabMap[name]), // for component tabs + source: z.string().optional(), + tabName: z.string().optional(), sortValue: z.number().optional(), // used for sorting nav entries, cssPrefix: z .union([ diff --git a/src/globals.ts b/src/globals.ts index a2d80d9..29f4d0a 100644 --- a/src/globals.ts +++ b/src/globals.ts @@ -1,4 +1,4 @@ -export const componentTabs: any = {}; +export const tabsDictionary: any = {}; export const tabNames: any = { 'react': 'React', @@ -9,14 +9,14 @@ export const tabNames: any = { }; export const buildTab = (entry: any, tab: string) => { - const tabEntry = componentTabs[entry.data.id] + const tabEntry = tabsDictionary[entry.data.id] // if no dictionary entry exists, and tab data exists if(tabEntry === undefined && tab) { - componentTabs[entry.data.id] = [tab] + tabsDictionary[entry.data.id] = [tab] // if dictionary entry & tab data exists, and entry does not include tab } else if (tabEntry && tab && !tabEntry.includes(tab)) { - componentTabs[entry.data.id] = [...tabEntry, tab]; + tabsDictionary[entry.data.id] = [...tabEntry, tab]; } } @@ -47,7 +47,7 @@ export const sortTabs = () => { // Sort tabs entries based on above sort order // Ensures all tabs are displayed in a consistent order & which tab gets displayed for a component route without a tab - Object.values(componentTabs).map((tabs: any) => { + Object.values(tabsDictionary).map((tabs: any) => { tabs.sort(sortSources) }) } \ No newline at end of file diff --git a/src/pages/[section]/[...page].astro b/src/pages/[section]/[...page].astro index 3f5ff11..4dfe257 100644 --- a/src/pages/[section]/[...page].astro +++ b/src/pages/[section]/[...page].astro @@ -3,8 +3,8 @@ import { getCollection, render } from 'astro:content' import { Title, PageSection } from '@patternfly/react-core' import MainLayout from '../../layouts/Main.astro' import { content } from '../../content' -import { kebabCase } from '../../utils/case' -import { componentTabs } from '../../globals' +import { kebabCase, getDefaultTab } from '../../utils' +import { buildTab, tabsDictionary } from '../../globals' import SectionGallery from '../../components/section-gallery/SectionGallery.astro' import LiveExample from '../../components/LiveExample.astro' import { @@ -36,24 +36,39 @@ export async function getStaticPaths() { ), ) - return collections.flat().map((entry) => ({ - params: { page: kebabCase(entry.data.id), section: entry.data.section }, - props: { - entry, - title: entry.data.title, - propComponents: entry.data.propComponents, - cssPrefix: entry.data.cssPrefix, - }, - })) + return collections.flat().map((entry) => { + const { filePath } = entry + const { tab, source, tabName } = entry.data + const hasTab = !!tab || !!source || !!tabName + + if (hasTab) { + buildTab(entry, tab) + } + + if (!hasTab) { + const defaultTab = getDefaultTab(filePath) + buildTab(entry, defaultTab) + } + + return { + params: { page: kebabCase(entry.data.id), section: entry.data.section }, + props: { + entry, + title: entry.data.title, + propComponents: entry.data.propComponents, + cssPrefix: entry.data.cssPrefix, + }, + } + }) } const { entry, propComponents, cssPrefix } = Astro.props const { title, id, section } = entry.data const { Content } = await render(entry) -if (section === 'components' && componentTabs[id]) { - // if section is components & tab exists, rewrite to first tab content - return Astro.rewrite(`/components/${kebabCase(id)}/${componentTabs[id][0]}`) +if (tabsDictionary[id]) { + // if tab exists, rewrite to first tab content + return Astro.rewrite(`/${section}/${kebabCase(id)}/${tabsDictionary[id][0]}`) } --- diff --git a/src/pages/[section]/[page]/[...tab].astro b/src/pages/[section]/[page]/[tab].astro similarity index 74% rename from src/pages/[section]/[page]/[...tab].astro rename to src/pages/[section]/[page]/[tab].astro index a76c853..667ef1f 100644 --- a/src/pages/[section]/[page]/[...tab].astro +++ b/src/pages/[section]/[page]/[tab].astro @@ -1,10 +1,11 @@ --- import { getCollection, render } from 'astro:content' import { Title, PageSection } from '@patternfly/react-core' +import { sentenceCase } from 'change-case' import MainLayout from '../../../layouts/Main.astro' import { content } from '../../../content' import { kebabCase } from '../../../utils/case' -import { componentTabs, tabNames, buildTab, sortTabs } from '../../../globals' +import { tabsDictionary, tabNames, buildTab, sortTabs } from '../../../globals' import { h1, h2, @@ -27,6 +28,7 @@ import { } from '../../../components/Content' import LiveExample from '../../../components/LiveExample.astro' import DocsTables from '../../../components/DocsTables.astro' +import { addDemosOrDeprecated, getDefaultTab } from '../../../utils' export async function getStaticPaths() { const collections = await Promise.all( @@ -35,33 +37,42 @@ export async function getStaticPaths() { ), ) - const flatCol = collections.flat() + const flatCol = collections + .flat() + .map(({ data, filePath, ...rest }) => ({ + filePath, + ...rest, + data: { + ...data, + tab: data.tab || data.source || getDefaultTab(filePath), + }, + })) .filter((entry) => entry.data.tab) // only pages with a tab should match this route .map((entry) => { // Build tabs dictionary - let tab = entry.data.tab; - if(tab) { // check for demos/deprecated - if(entry.id.includes('demos')) { - tab = `${tab}-demos`; - } else if (entry.id.includes('deprecated')) { - tab = `${tab}-deprecated`; - } + const tab = addDemosOrDeprecated(entry.data.tab, entry.id) + buildTab(entry, tab) + if (entry.data.tabName) { + tabNames[entry.data.tab] = entry.data.tabName } - buildTab(entry, tab); - return { - params: { page: kebabCase(entry.data.id), section: entry.data.section, tab }, + params: { + page: kebabCase(entry.data.id), + section: entry.data.section, + tab, + }, props: { entry, ...entry.data }, } }) sortTabs() + return flatCol } const { entry, propComponents, cssPrefix } = Astro.props - const { title, id, section } = entry.data + const { Content } = await render(entry) const currentPath = Astro.url.pathname --- @@ -75,7 +86,7 @@ const currentPath = Astro.url.pathname ) } { - componentTabs[id] && ( + tabsDictionary[id] && (
    - {componentTabs[id].map((tab: string) => ( + {tabsDictionary[id].map((tab: string) => ( // eslint-disable-next-line react/jsx-key
  • - {tabNames[tab]} + {tabNames[tab] || sentenceCase(tab)}
  • ))} diff --git a/src/utils/__tests__/packageUtils.test.ts b/src/utils/__tests__/packageUtils.test.ts new file mode 100644 index 0000000..fbbc4fe --- /dev/null +++ b/src/utils/__tests__/packageUtils.test.ts @@ -0,0 +1,194 @@ +import { getPackageName, getTabBase, getDefaultTab, addDemosOrDeprecated } from '../packageUtils' + +describe('getPackageName', () => { + it('returns empty string for empty input', () => { + expect(getPackageName('')).toBe('') + }) + + it('returns empty string for null/undefined input', () => { + expect(getPackageName(null as any)).toBe('') + expect(getPackageName(undefined)).toBe('') + expect(getPackageName()).toBe('') + }) + + it('extracts scoped package name correctly', () => { + const filePath = '/path/to/node_modules/@patternfly/react-core/dist/index.js' + expect(getPackageName(filePath)).toBe('@patternfly/react-core') + }) + + it('extracts scoped package name with multiple scoped packages', () => { + const filePath = '/path/to/node_modules/@patternfly/patternfly/dist/index.js' + expect(getPackageName(filePath)).toBe('@patternfly/patternfly') + }) + + it('extracts non-scoped package name correctly', () => { + const filePath = '/path/to/node_modules/react/dist/index.js' + expect(getPackageName(filePath)).toBe('react') + }) + + it('handles path without node_modules', () => { + const filePath = '/path/to/some/file.js' + expect(getPackageName(filePath)).toBe('') + }) + + it('handles complex nested path with scoped package', () => { + const filePath = '/Users/dev/project/node_modules/@patternfly/react-core/lib/components/Button/Button.js' + expect(getPackageName(filePath)).toBe('@patternfly/react-core') + }) + + it('handles Windows-style paths with scoped package', () => { + const filePath = 'C:\\Users\\dev\\project\\node_modules\\@patternfly\\react-core\\dist\\index.js' + expect(getPackageName(filePath)).toBe('@patternfly/react-core') + }) +}) + +describe('getTabBase', () => { + it('returns "html" for @patternfly/patternfly package', () => { + expect(getTabBase('@patternfly/patternfly')).toBe('html') + }) + + it('returns "react" for @patternfly/react-core package', () => { + expect(getTabBase('@patternfly/react-core')).toBe('react') + }) + + it('returns empty string for unknown package', () => { + expect(getTabBase('unknown-package')).toBe('') + }) + + it('returns empty string for empty input', () => { + expect(getTabBase('')).toBe('') + }) + + it('returns empty string for null/undefined input', () => { + expect(getTabBase(null as any)).toBe('') + expect(getTabBase(undefined as any)).toBe('') + }) + + it('is case sensitive', () => { + expect(getTabBase('@PatternFly/patternfly')).toBe('') + expect(getTabBase('@patternfly/PatternFly')).toBe('') + }) +}) + +describe('addDemosOrDeprecated', () => { + it('returns empty string when filePath is missing', () => { + expect(addDemosOrDeprecated('html', undefined)).toBe('') + expect(addDemosOrDeprecated('html', '')).toBe('') + expect(addDemosOrDeprecated('html', null as any)).toBe('') + }) + + it('returns empty string when tabName is missing', () => { + expect(addDemosOrDeprecated('', '/path/to/file.js')).toBe('') + }) + + it('returns empty string when both parameters are missing', () => { + expect(addDemosOrDeprecated('', undefined)).toBe('') + }) + + it('returns original tabName when no demos or deprecated in filePath/tabName', () => { + expect(addDemosOrDeprecated('html', '/path/to/file.js')).toBe('html') + expect(addDemosOrDeprecated('react', '/path/to/file.js')).toBe('react') + }) + + it('adds -demos suffix when filePath contains demos', () => { + expect(addDemosOrDeprecated('html', '/path/to/demos/file.js')).toBe('html-demos') + expect(addDemosOrDeprecated('react', '/path/to/demos/file.js')).toBe('react-demos') + }) + + it('adds -deprecated suffix when filePath contains deprecated', () => { + expect(addDemosOrDeprecated('html', '/path/to/deprecated/file.js')).toBe('html-deprecated') + expect(addDemosOrDeprecated('react', '/path/to/deprecated/file.js')).toBe('react-deprecated') + }) + + it('adds both -demos and -deprecated when both are in filePath', () => { + expect(addDemosOrDeprecated('html', '/path/to/demos/deprecated/file.js')).toBe('html-demos-deprecated') + expect(addDemosOrDeprecated('react', '/path/to/demos/deprecated/file.js')).toBe('react-demos-deprecated') + }) + + it('does not add duplicate -demos suffix when tabName already contains -demos', () => { + expect(addDemosOrDeprecated('html-demos', '/path/to/demos/file.js')).toBe('html-demos') + expect(addDemosOrDeprecated('react-demos', '/path/to/demos/file.js')).toBe('react-demos') + }) + + it('does not add duplicate -deprecated suffix when tabName already contains -deprecated', () => { + expect(addDemosOrDeprecated('html-deprecated', '/path/to/deprecated/file.js')).toBe('html-deprecated') + expect(addDemosOrDeprecated('react-deprecated', '/path/to/deprecated/file.js')).toBe('react-deprecated') + }) + + it('adds only missing suffix when tabName already has one suffix', () => { + expect(addDemosOrDeprecated('html-demos', '/path/to/demos/deprecated/file.js')).toBe('html-demos-deprecated') + expect(addDemosOrDeprecated('react-deprecated', '/path/to/demos/deprecated/file.js')).toBe('react-deprecated-demos') + }) + + it('does not add any suffix when tabName already has both suffixes', () => { + expect(addDemosOrDeprecated('html-demos-deprecated', '/path/to/demos/deprecated/file.js')).toBe('html-demos-deprecated') + expect(addDemosOrDeprecated('react-deprecated-demos', '/path/to/demos/deprecated/file.js')).toBe('react-deprecated-demos') + }) +}) + +describe('getDefaultTab', () => { + it('returns base tab for regular patternfly package path', () => { + const filePath = '/path/to/node_modules/@patternfly/patternfly/dist/index.js' + expect(getDefaultTab(filePath)).toBe('html') + }) + + it('returns base tab for regular react-core package path', () => { + const filePath = '/path/to/node_modules/@patternfly/react-core/dist/index.js' + expect(getDefaultTab(filePath)).toBe('react') + }) + + it('returns demos tab for demos path with patternfly package', () => { + const filePath = '/path/to/node_modules/@patternfly/patternfly/demos/Button.js' + expect(getDefaultTab(filePath)).toBe('html-demos') + }) + + it('returns demos tab for demos path with react-core package', () => { + const filePath = '/path/to/node_modules/@patternfly/react-core/demos/Button.js' + expect(getDefaultTab(filePath)).toBe('react-demos') + }) + + it('returns deprecated tab for deprecated path with patternfly package', () => { + const filePath = '/path/to/node_modules/@patternfly/patternfly/deprecated/OldButton.js' + expect(getDefaultTab(filePath)).toBe('html-deprecated') + }) + + it('returns deprecated tab for deprecated path with react-core package', () => { + const filePath = '/path/to/node_modules/@patternfly/react-core/deprecated/OldButton.js' + expect(getDefaultTab(filePath)).toBe('react-deprecated') + }) + + it('adds both demos and deprecated when both are in path', () => { + const filePath = '/path/to/node_modules/@patternfly/react-core/demos/deprecated/Button.js' + expect(getDefaultTab(filePath)).toBe('react-demos-deprecated') + }) + + it('returns empty string for unknown package', () => { + const filePath = '/path/to/node_modules/unknown-package/dist/index.js' + expect(getDefaultTab(filePath)).toBe('') + }) + + it('returns empty string for path without node_modules', () => { + const filePath = '/path/to/some/file.js' + expect(getDefaultTab(filePath)).toBe('') + }) + + it('handles empty input', () => { + expect(getDefaultTab('')).toBe('') + }) + + it('handles null/undefined input', () => { + expect(getDefaultTab(null as any)).toBe('') + expect(getDefaultTab(undefined)).toBe('') + expect(getDefaultTab()).toBe('') + }) + + it('returns empty string for unknown package with demos path (empty tabBase causes addDemosOrDeprecated to return empty)', () => { + const filePath = '/path/to/node_modules/unknown-package/demos/Button.js' + expect(getDefaultTab(filePath)).toBe('') + }) + + it('returns empty string for unknown package with deprecated path (empty tabBase causes addDemosOrDeprecated to return empty)', () => { + const filePath = '/path/to/node_modules/unknown-package/deprecated/Button.js' + expect(getDefaultTab(filePath)).toBe('') + }) +}) diff --git a/src/utils/index.ts b/src/utils/index.ts index e81ff96..22b7a66 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -1,3 +1,4 @@ export * from './capitalize' export * from './slugger' export * from './case' +export * from './packageUtils' diff --git a/src/utils/packageUtils.ts b/src/utils/packageUtils.ts new file mode 100644 index 0000000..c47babe --- /dev/null +++ b/src/utils/packageUtils.ts @@ -0,0 +1,67 @@ +export const getPackageName = (filePath?: string): string => { + if (!filePath) { + return '' + } + + // Normalize path separators for cross-platform compatibility + const normalizedPath = filePath.replace(/\\/g, '/') + const splitPath = normalizedPath.split('/') + + // check if it's a scoped package or not + if (normalizedPath.includes('node_modules/@')) { + const scopeIndex = splitPath.findIndex((path) => path.startsWith('@')) + const scope = splitPath[scopeIndex] + const packageName = splitPath[scopeIndex + 1] + return `${scope}/${packageName}` + } + + const nodeModulesIndex = splitPath.findIndex( + (path) => path === 'node_modules', + ) + + if (nodeModulesIndex === -1) { + return '' + } + + const packageName = splitPath[nodeModulesIndex + 1] + return packageName || '' +} + +// this isn't something I love, but it should be temporary along with the rest of the default tab logic, +// once we update our docs to no longer need this feature we should remove it. +// issue https://github.com/patternfly/patternfly-doc-core/issues/164 also tracks this. +export const getTabBase = (packageName: string): string => { + switch (packageName) { + case '@patternfly/patternfly': + return 'html' + case '@patternfly/react-core': + return 'react' + default: + return '' + } +} + +export const addDemosOrDeprecated = (tabName: string, filePath?: string) => { + if (!filePath || !tabName) { + return '' + } + + if (filePath.includes('demos') && !tabName.includes('-demos')) { + tabName += '-demos' + } + + if (filePath.includes('deprecated') && !tabName.includes('-deprecated')) { + tabName += '-deprecated' + } + + return tabName +} + +export const getDefaultTab = (filePath?: string): string => { + const packageName = getPackageName(filePath) + const tabBase = getTabBase(packageName) + + const tab = addDemosOrDeprecated(tabBase, filePath) + + return tab +}