forked from WordPress/gutenberg
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Components: Set up README auto-generator (WordPress#66035)
* Add components readme generator * Move * AlignmentMatrixControl: Add missing `@deprecated` tag * Add docs manifest for AlignmentMatrixControl * Handle case with no subcomponents * Add JSON schema * Commit AlignmentMatrixControl readme changes * Fixup: Handle case with no subcomponents * Add manifest for AnglePickerControl * Simplify * Improve schema descriptions * Handle docgen errors * Convert to async * Move glob further up * Handle unparseable JSON * Handle write file progress * Fixup * Apply feedback in markdown props handling * Simplify * Handle cases when `displayName` in manifest is wrong Co-authored-by: mirka <[email protected]> Co-authored-by: ciampo <[email protected]> Co-authored-by: tyxla <[email protected]>
- Loading branch information
1 parent
4b5c369
commit 3bcc09c
Showing
11 changed files
with
366 additions
and
60 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,112 @@ | ||
/** | ||
* External dependencies | ||
*/ | ||
import docgen from 'react-docgen-typescript'; | ||
import glob from 'glob'; | ||
import fs from 'node:fs/promises'; | ||
import path from 'path'; | ||
|
||
/** | ||
* Internal dependencies | ||
*/ | ||
import { generateMarkdownDocs } from './markdown/index.mjs'; | ||
|
||
const MANIFEST_GLOB = 'packages/components/src/**/docs-manifest.json'; | ||
|
||
// For consistency, options should generally match the options used in Storybook. | ||
const OPTIONS = { | ||
shouldExtractLiteralValuesFromEnum: true, | ||
shouldRemoveUndefinedFromOptional: true, | ||
propFilter: ( prop ) => | ||
prop.parent ? ! /node_modules/.test( prop.parent.fileName ) : true, | ||
savePropValueAsString: true, | ||
}; | ||
|
||
function getTypeDocsForComponent( { | ||
manifestPath, | ||
componentFilePath, | ||
displayName, | ||
} ) { | ||
const resolvedPath = path.resolve( | ||
path.dirname( manifestPath ), | ||
componentFilePath | ||
); | ||
|
||
const typeDocs = docgen.parse( resolvedPath, OPTIONS ); | ||
|
||
if ( typeDocs.length === 0 ) { | ||
throw new Error( | ||
`react-docgen-typescript could not generate any type docs from ${ resolvedPath }` | ||
); | ||
} | ||
|
||
const matchingTypeDoc = typeDocs.find( | ||
( obj ) => obj.displayName === displayName | ||
); | ||
|
||
if ( typeof matchingTypeDoc === 'undefined' ) { | ||
const unmatchedTypeDocs = typeDocs | ||
.map( ( obj ) => `\`${ obj.displayName }\`` ) | ||
.join( ', ' ); | ||
|
||
throw new Error( | ||
`react-docgen-typescript could not find type docs for ${ displayName } in ${ resolvedPath }. (Found ${ unmatchedTypeDocs })` | ||
); | ||
} | ||
|
||
return matchingTypeDoc; | ||
} | ||
|
||
async function parseManifest( manifestPath ) { | ||
try { | ||
return JSON.parse( await fs.readFile( manifestPath, 'utf8' ) ); | ||
} catch ( e ) { | ||
throw new Error( | ||
`Error parsing docs manifest at ${ manifestPath }: ${ e.message }` | ||
); | ||
} | ||
} | ||
|
||
const manifests = glob.sync( MANIFEST_GLOB ); | ||
|
||
await Promise.all( | ||
manifests.map( async ( manifestPath ) => { | ||
const manifest = await parseManifest( manifestPath ); | ||
|
||
const typeDocs = getTypeDocsForComponent( { | ||
manifestPath, | ||
componentFilePath: manifest.filePath, | ||
displayName: manifest.displayName, | ||
} ); | ||
|
||
const subcomponentTypeDocs = manifest.subcomponents?.map( | ||
( subcomponent ) => { | ||
const docs = getTypeDocsForComponent( { | ||
manifestPath, | ||
componentFilePath: subcomponent.filePath, | ||
displayName: subcomponent.displayName, | ||
} ); | ||
|
||
if ( subcomponent.preferredDisplayName ) { | ||
docs.displayName = subcomponent.preferredDisplayName; | ||
} | ||
|
||
return docs; | ||
} | ||
); | ||
const docs = generateMarkdownDocs( { typeDocs, subcomponentTypeDocs } ); | ||
const outputFile = path.resolve( | ||
path.dirname( manifestPath ), | ||
'./README.md' | ||
); | ||
|
||
try { | ||
console.log( `Writing docs to ${ outputFile }` ); | ||
return fs.writeFile( outputFile, docs ); | ||
} catch ( e ) { | ||
throw new Error( | ||
`Error writing docs to ${ outputFile }: ${ e.message }` | ||
); | ||
} | ||
} ) | ||
); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
/** | ||
* External dependencies | ||
*/ | ||
import json2md from 'json2md'; | ||
|
||
/** | ||
* Internal dependencies | ||
*/ | ||
import { generateMarkdownPropsJson } from './props.mjs'; | ||
|
||
export function generateMarkdownDocs( { typeDocs, subcomponentTypeDocs } ) { | ||
const mainDocsJson = [ | ||
'<!-- This file is generated automatically and cannot be edited directly. -->\n', | ||
{ h1: typeDocs.displayName }, | ||
{ | ||
p: `<p class="callout callout-info">See the <a href="https://wordpress.github.io/gutenberg/?path=/docs/components-${ typeDocs.displayName.toLowerCase() }--docs">WordPress Storybook</a> for more detailed, interactive documentation.</p>`, | ||
}, | ||
typeDocs.description, | ||
...generateMarkdownPropsJson( typeDocs.props ), | ||
]; | ||
|
||
const subcomponentDocsJson = subcomponentTypeDocs?.length | ||
? [ | ||
{ h2: 'Subcomponents' }, | ||
...subcomponentTypeDocs.flatMap( ( subcomponentTypeDoc ) => [ | ||
{ | ||
h3: subcomponentTypeDoc.displayName, | ||
}, | ||
subcomponentTypeDoc.description, | ||
...generateMarkdownPropsJson( subcomponentTypeDoc.props, { | ||
headingLevel: 4, | ||
} ), | ||
] ), | ||
] | ||
: []; | ||
|
||
return json2md( | ||
[ ...mainDocsJson, ...subcomponentDocsJson ].filter( Boolean ) | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
function renderPropType( type ) { | ||
const MAX_ENUM_VALUES = 10; | ||
|
||
switch ( type.name ) { | ||
case 'enum': { | ||
const string = type.value | ||
.slice( 0, MAX_ENUM_VALUES ) | ||
.map( ( { value } ) => value ) | ||
.join( ' | ' ); | ||
|
||
if ( type.value.length > MAX_ENUM_VALUES ) { | ||
return `${ string } | ...`; | ||
} | ||
return string; | ||
} | ||
default: | ||
return type.name; | ||
} | ||
} | ||
|
||
export function generateMarkdownPropsJson( props, { headingLevel = 2 } = {} ) { | ||
const sortedKeys = Object.keys( props ).sort( ( [ a ], [ b ] ) => | ||
a.localeCompare( b ) | ||
); | ||
|
||
const propsJson = sortedKeys | ||
.flatMap( ( key ) => { | ||
const prop = props[ key ]; | ||
|
||
if ( prop.description?.includes( '@ignore' ) ) { | ||
return null; | ||
} | ||
|
||
return [ | ||
{ [ `h${ headingLevel + 1 }` ]: `\`${ key }\`` }, | ||
prop.description, | ||
{ | ||
ul: [ | ||
`Type: \`${ renderPropType( prop.type ) }\``, | ||
`Required: ${ prop.required ? 'Yes' : 'No' }`, | ||
prop.defaultValue && | ||
`Default: \`${ prop.defaultValue.value }\``, | ||
].filter( Boolean ), | ||
}, | ||
]; | ||
} ) | ||
.filter( Boolean ); | ||
|
||
return [ { [ `h${ headingLevel }` ]: 'Props' }, ...propsJson ]; | ||
} | ||
|
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
{ | ||
"title": "JSON schema for @wordpress/components README manifests", | ||
"$schema": "http://json-schema.org/draft-07/schema#", | ||
"type": "object", | ||
"properties": { | ||
"displayName": { | ||
"type": "string", | ||
"description": "The `displayName` of the component, as determined in code. Used to identify the component in the specified source file." | ||
}, | ||
"filePath": { | ||
"type": "string", | ||
"description": "The file path where the component is located." | ||
}, | ||
"subcomponents": { | ||
"type": "array", | ||
"description": "List of subcomponents related to the component.", | ||
"items": { | ||
"type": "object", | ||
"properties": { | ||
"displayName": { | ||
"type": "string", | ||
"description": "The `displayName` of the subcomponent, as determined in code. Used to identify the component in the specified source file." | ||
}, | ||
"preferredDisplayName": { | ||
"type": "string", | ||
"description": "The display name to use in the README, if it is different from the `displayName` as determined in code." | ||
}, | ||
"filePath": { | ||
"type": "string", | ||
"description": "The file path where the subcomponent is located." | ||
} | ||
}, | ||
"required": [ "displayName", "filePath" ] | ||
} | ||
} | ||
}, | ||
"required": [ "displayName", "filePath" ] | ||
} |
Oops, something went wrong.