diff --git a/cli/__tests__/convertToMDX.test.ts b/cli/__tests__/convertToMDX.test.ts index 6e44cfe..c96d309 100644 --- a/cli/__tests__/convertToMDX.test.ts +++ b/cli/__tests__/convertToMDX.test.ts @@ -6,6 +6,7 @@ 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) })) jest.mock('glob', () => ({ diff --git a/cli/cli.ts b/cli/cli.ts index 558675d..b8bd759 100755 --- a/cli/cli.ts +++ b/cli/cli.ts @@ -13,14 +13,14 @@ import { buildPropsData } from './buildPropsData.js' import { hasFile } from './hasFile.js' import { convertToMDX } from './convertToMDX.js' -function updateContent(program: Command) { +async function updateContent(program: Command) { const { verbose } = program.opts() if (verbose) { console.log('Verbose mode enabled') } - createCollectionContent( + await createCollectionContent( astroRoot, `${process.cwd()}/pf-docs.config.mjs`, verbose, @@ -45,39 +45,58 @@ async function generateProps(program: Command, forceProps: boolean = false) { verbose, ) } + +async function transformMDContentToMDX() { + const config = await getConfig(`${currentDir}/pf-docs.config.mjs`) + if (!config) { + console.error( + 'No config found, please run the `setup` command or manually create a pf-docs.config.mjs file', + ) + return config + } + + if (config.content) { + await Promise.all( + config.content.map((contentObj) => convertToMDX(contentObj.pattern)), + ) + } +} + async function buildProject(): Promise { - updateContent(program) + await updateContent(program) await generateProps(program, true) const config = await getConfig(`${currentDir}/pf-docs.config.mjs`) if (!config) { console.error( 'No config found, please run the `setup` command or manually create a pf-docs.config.mjs file', ) - return config; + return config } if (!config.outputDir) { console.error( "No outputDir found in config file, an output directory must be defined in your config file e.g. 'dist'", ) - return config; + return config } + await transformMDContentToMDX() + build({ root: astroRoot, outDir: join(currentDir, config.outputDir) }) - - return config; + + return config } async function deploy() { const { verbose } = program.opts() - + if (verbose) { console.log('Starting Cloudflare deployment...') } try { // First build the project - const config = await buildProject(); + const config = await buildProject() if (config) { if (verbose) { console.log('Build complete, deploying to Cloudflare...') @@ -86,12 +105,12 @@ async function deploy() { // Deploy using Wrangler const { execSync } = await import('child_process') const outputPath = join(currentDir, config.outputDir) - + execSync(`npx wrangler pages deploy ${outputPath}`, { stdio: 'inherit', - cwd: currentDir + cwd: currentDir, }) - + console.log('Successfully deployed to Cloudflare Pages!') } } catch (error) { @@ -143,7 +162,7 @@ program.command('init').action(async () => { }) program.command('start').action(async () => { - updateContent(program) + await updateContent(program) // if a props file hasn't been generated yet, but the consumer has propsData, it will cause a runtime error so to // prevent that we're just creating a props file regardless of what they say if one doesn't exist yet @@ -153,7 +172,7 @@ program.command('start').action(async () => { }) program.command('build').action(async () => { - await buildProject(); + await buildProject() }) program.command('generate-props').action(async () => { @@ -162,7 +181,7 @@ program.command('generate-props').action(async () => { }) program.command('serve').action(async () => { - updateContent(program) + await updateContent(program) preview({ root: astroRoot }) }) @@ -178,7 +197,7 @@ program }) program.command('deploy').action(async () => { - await deploy() + await deploy() }) program.parse(process.argv) diff --git a/cli/convertToMDX.ts b/cli/convertToMDX.ts index 3186b68..425249c 100644 --- a/cli/convertToMDX.ts +++ b/cli/convertToMDX.ts @@ -1,4 +1,4 @@ -import { readFile, writeFile, unlink } from 'fs/promises' +import { readFile, writeFile, unlink, access } from 'fs/promises' import { glob } from 'glob' import path from 'path' @@ -46,7 +46,7 @@ function removeNoLiveTags(content: string): string { function removeExistingImports(content: string): string { // Remove imports that don't end in .css - const importRegex = /^import {?[\w\s,\n]*}? from ['"](?!.*\.css['"])[^'"]*['"]\n/gm + const importRegex = /^import {?[\w\s,\n]*}? from ['"](?!.*\.css['"])[^'"]*['"];?\n/gm return content.replace(importRegex, '') } @@ -57,8 +57,15 @@ function convertCommentsToMDX(content: string): string { ) } +async function fileExists(file: string): Promise { + return access(file).then(() => true).catch(() => false) +} + async function processFile(file: string): Promise { - if (file.endsWith('.mdx')) { + const exists = await fileExists(file) + + // if the file is already an mdx file or doesn't exist we don't need to do anything + if (file.endsWith('.mdx') || !exists) { return } diff --git a/cli/createCollectionContent.ts b/cli/createCollectionContent.ts index 368f092..58b600a 100644 --- a/cli/createCollectionContent.ts +++ b/cli/createCollectionContent.ts @@ -1,5 +1,6 @@ /* eslint-disable no-console */ import { writeFile } from 'fs/promises' +import { join } from 'path' import { getConfig } from './getConfig.js' export async function createCollectionContent(rootDir: string, configFile: string, verbose: boolean) { @@ -14,7 +15,7 @@ export async function createCollectionContent(rootDir: string, configFile: strin return } - const contentFile = rootDir + 'src/content.ts' + const contentFile = join(rootDir, 'src', 'content.ts') try { await writeFile( diff --git a/package-lock.json b/package-lock.json index 2f7b899..540f31c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18,6 +18,7 @@ "@patternfly/patternfly": "^6.0.0", "@patternfly/react-code-editor": "^6.2.2", "@patternfly/react-core": "^6.0.0", + "@patternfly/react-drag-drop": "^6.0.0", "@patternfly/react-icons": "^6.0.0", "@patternfly/react-styles": "^6.0.0", "@patternfly/react-table": "^6.0.0", @@ -1391,6 +1392,73 @@ "@jridgewell/sourcemap-codec": "^1.4.10" } }, + "node_modules/@dnd-kit/accessibility": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@dnd-kit/accessibility/-/accessibility-3.1.1.tgz", + "integrity": "sha512-2P+YgaXF+gRsIihwwY1gCsQSYnu9Zyj2py8kY5fFvUM1qm2WA2u639R6YNVfU4GWr+ZM5mqEsfHZZLoRONbemw==", + "license": "MIT", + "dependencies": { + "tslib": "^2.0.0" + }, + "peerDependencies": { + "react": ">=16.8.0" + } + }, + "node_modules/@dnd-kit/core": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/@dnd-kit/core/-/core-6.3.1.tgz", + "integrity": "sha512-xkGBRQQab4RLwgXxoqETICr6S5JlogafbhNsidmrkVv2YRs5MLwpjoF2qpiGjQt8S9AoxtIV603s0GIUpY5eYQ==", + "license": "MIT", + "dependencies": { + "@dnd-kit/accessibility": "^3.1.1", + "@dnd-kit/utilities": "^3.2.2", + "tslib": "^2.0.0" + }, + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, + "node_modules/@dnd-kit/modifiers": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/@dnd-kit/modifiers/-/modifiers-9.0.0.tgz", + "integrity": "sha512-ybiLc66qRGuZoC20wdSSG6pDXFikui/dCNGthxv4Ndy8ylErY0N3KVxY2bgo7AWwIbxDmXDg3ylAFmnrjcbVvw==", + "license": "MIT", + "dependencies": { + "@dnd-kit/utilities": "^3.2.2", + "tslib": "^2.0.0" + }, + "peerDependencies": { + "@dnd-kit/core": "^6.3.0", + "react": ">=16.8.0" + } + }, + "node_modules/@dnd-kit/sortable": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/@dnd-kit/sortable/-/sortable-10.0.0.tgz", + "integrity": "sha512-+xqhmIIzvAYMGfBYYnbKuNicfSsk4RksY2XdmJhT+HAC01nix6fHCztU68jooFiMUB01Ky3F0FyOvhG/BZrWkg==", + "license": "MIT", + "dependencies": { + "@dnd-kit/utilities": "^3.2.2", + "tslib": "^2.0.0" + }, + "peerDependencies": { + "@dnd-kit/core": "^6.3.0", + "react": ">=16.8.0" + } + }, + "node_modules/@dnd-kit/utilities": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/@dnd-kit/utilities/-/utilities-3.2.2.tgz", + "integrity": "sha512-+MKAJEOfaBe5SmV6t34p80MMKhjvUz0vRrvVJbPT0WElzaOJ/1xs+D+KDv+tD/NE5ujfrChEcshd4fLn0wpiqg==", + "license": "MIT", + "dependencies": { + "tslib": "^2.0.0" + }, + "peerDependencies": { + "react": ">=16.8.0" + } + }, "node_modules/@emmetio/abbreviation": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/@emmetio/abbreviation/-/abbreviation-2.3.3.tgz", @@ -3804,6 +3872,25 @@ "react-dom": "^17 || ^18" } }, + "node_modules/@patternfly/react-drag-drop": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/@patternfly/react-drag-drop/-/react-drag-drop-6.2.2.tgz", + "integrity": "sha512-MBPkOz6VitwmRzjJIbS4i8/AML35PqSs5sBXZRk8ryCq/CgZ2j0zV2B5L7Z8UMwHRNdIlNJDIrUvTo+J1A9KsA==", + "license": "MIT", + "dependencies": { + "@dnd-kit/core": "^6.3.1", + "@dnd-kit/modifiers": "^9.0.0", + "@dnd-kit/sortable": "^10.0.0", + "@patternfly/react-core": "^6.2.2", + "@patternfly/react-icons": "^6.2.2", + "@patternfly/react-styles": "^6.2.2", + "resize-observer-polyfill": "^1.5.1" + }, + "peerDependencies": { + "react": "^17 || ^18", + "react-dom": "^17 || ^18" + } + }, "node_modules/@patternfly/react-icons": { "version": "6.2.2", "resolved": "https://registry.npmjs.org/@patternfly/react-icons/-/react-icons-6.2.2.tgz", @@ -21124,6 +21211,12 @@ "dev": true, "license": "MIT" }, + "node_modules/resize-observer-polyfill": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz", + "integrity": "sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg==", + "license": "MIT" + }, "node_modules/resolve": { "version": "1.22.8", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", diff --git a/package.json b/package.json index c12c9d6..b0e046b 100644 --- a/package.json +++ b/package.json @@ -7,9 +7,12 @@ "access": "public" }, "scripts": { - "dev": "astro dev", - "start": "astro dev", - "build": "astro check && astro build", + "dev": "npm run start:cli", + "start": "npm run dev", + "start:cli": "npm run build:cli && node ./dist/cli/cli.js start", + "start:astro": "astro dev", + "build": "npm run build:cli && node ./dist/cli/cli.js build", + "build:astro": "astro check && astro build", "build:cli": "tsc --build ./cli/tsconfig.json", "build:cli:watch": "tsc --build --watch ./cli/tsconfig.json", "build:props": "npm run build:cli && node ./dist/cli/cli.js generate-props", @@ -52,6 +55,7 @@ "@patternfly/patternfly": "^6.0.0", "@patternfly/react-code-editor": "^6.2.2", "@patternfly/react-core": "^6.0.0", + "@patternfly/react-drag-drop": "^6.0.0", "@patternfly/react-icons": "^6.0.0", "@patternfly/react-styles": "^6.0.0", "@patternfly/react-table": "^6.0.0", diff --git a/src/components/DocsTables.astro b/src/components/DocsTables.astro index 81f3ffe..6a0300e 100644 --- a/src/components/DocsTables.astro +++ b/src/components/DocsTables.astro @@ -1,10 +1,12 @@ --- import { Stack, StackItem } from '@patternfly/react-core' -import PropsTables from './PropsTables.astro' +import { PropsTables } from './PropsTables' import CSSTable from './CSSTable.astro' const { propComponents, cssPrefix } = Astro.props +const { url } = Astro + const hasTables = !!propComponents || !!cssPrefix --- @@ -14,12 +16,16 @@ const hasTables = !!propComponents || !!cssPrefix {propComponents && ( - + )} {cssPrefix && ( - + )} diff --git a/src/components/LiveExample.tsx b/src/components/LiveExample.tsx index b433a9b..ea6dd3d 100644 --- a/src/components/LiveExample.tsx +++ b/src/components/LiveExample.tsx @@ -10,6 +10,7 @@ import { convertToReactComponent } from '@patternfly/ast-helpers' import { ErrorBoundary } from 'react-error-boundary' import * as reactCoreModule from '@patternfly/react-core' import * as reactIconsModule from '@patternfly/react-icons' +import * as reactDragDropModule from '@patternfly/react-drag-drop' import styles from '@patternfly/react-styles/css/components/_index' import * as reactTokensModule from '@patternfly/react-tokens' import { ExampleToolbar } from './ExampleToolbar' @@ -33,6 +34,7 @@ function getLivePreview(editorCode: string) { const scope = { ...reactCoreModule, ...reactIconsModule, + ...reactDragDropModule, styles, ...reactTokensModule, ...{ useState, Fragment, useRef, useEffect, createRef, useReducer }, diff --git a/src/components/PropsTables.astro b/src/components/PropsTables.astro deleted file mode 100644 index 18390cf..0000000 --- a/src/components/PropsTables.astro +++ /dev/null @@ -1,52 +0,0 @@ ---- -import { PropsTable } from './PropsTable' -import { Stack, StackItem } from '@patternfly/react-core' -import { AutoLinkHeader } from './AutoLinkHeader' - -async function getPropsData(propComponents?: string[]) { - if (!propComponents || propComponents.length === 0) { - return [] - } - - const url = new URL(`/props?components=${propComponents}`, Astro.url) - - try { - const response = await fetch(url) - const propsData = await response.json() - return propsData - } catch (e) { - // eslint-disable-next-line no-console - console.error(e) - return [] - } -} - -const { propComponents } = Astro.props - -const propsData = await getPropsData(propComponents) ---- - -{ - - - - Props - - - {propsData - .filter((comp: any) => !!comp) - .map((component: any) => ( - - - - ))} - -} diff --git a/src/components/PropsTables.tsx b/src/components/PropsTables.tsx new file mode 100644 index 0000000..3c15363 --- /dev/null +++ b/src/components/PropsTables.tsx @@ -0,0 +1,62 @@ +import { FunctionComponent, useEffect, useState } from 'react' +import { PropsTable } from './PropsTable' +import { Stack, StackItem } from '@patternfly/react-core' +import { AutoLinkHeader } from './AutoLinkHeader' + +interface PropsTablesProps { + propComponents: string[] + url: string +} + +async function getPropsData(propComponents: string[], urlBase: string) { + if (!propComponents || propComponents.length === 0) { + return [] + } + + const url = new URL(`/props?components=${propComponents}`, urlBase) + + try { + const response = await fetch(url.toString()) + const propsData = await response.json() + return propsData + } catch (e) { + // eslint-disable-next-line no-console + console.error(e) + return [] + } +} + +export const PropsTables: FunctionComponent = ({ + propComponents, + url, +}) => { + const [propsData, setPropsData] = useState([]) + useEffect(() => { + getPropsData(propComponents, url).then(setPropsData) + }, [propComponents, url]) + + return ( + + + + Props + + + {propsData + .filter((comp: any) => !!comp) + .map((component: any) => ( + + + + ))} + + ) +} diff --git a/src/content.config.ts b/src/content.config.ts index 161fd91..29f09dc 100644 --- a/src/content.config.ts +++ b/src/content.config.ts @@ -3,7 +3,6 @@ import { glob } from 'astro/loaders' import { content } from './content' import type { CollectionDefinition } from '../cli/getConfig' -import { convertToMDX } from '../cli/convertToMDX' function defineContent(contentObj: CollectionDefinition) { const { base, packageName, pattern, name } = contentObj @@ -16,12 +15,11 @@ function defineContent(contentObj: CollectionDefinition) { } // TODO: Expand for other packages that remain under the react umbrella (Table, CodeEditor, etc) - const tabMap: any = { + const tabMap: Record = { 'react-component-docs': 'react', 'core-component-docs': 'html', } - convertToMDX(pattern) const mdxPattern = pattern.replace(/\.md$/, '.mdx') return defineCollection({ @@ -36,7 +34,7 @@ function defineContent(contentObj: CollectionDefinition) { sortValue: z.number().optional(), // used for sorting nav entries, cssPrefix: z .union([ - z.string().transform((val) => [val]), + z.string().transform((val: string) => [val]), z.array(z.string()), z.null().transform(() => undefined), ]) diff --git a/src/pages/[section]/[...page].astro b/src/pages/[section]/[...page].astro index 3d8b86c..3f5ff11 100644 --- a/src/pages/[section]/[...page].astro +++ b/src/pages/[section]/[...page].astro @@ -90,10 +90,6 @@ if (section === 'components' && componentTabs[id]) { LiveExample, }} /> - + diff --git a/src/pages/[section]/[page]/[...tab].astro b/src/pages/[section]/[page]/[...tab].astro index 311d17a..a76c853 100644 --- a/src/pages/[section]/[page]/[...tab].astro +++ b/src/pages/[section]/[page]/[...tab].astro @@ -125,6 +125,6 @@ const currentPath = Astro.url.pathname LiveExample, }} /> - +