import { isString } from '../../utils/unit.js'; import { ComponentMeta } from '../ComponentMeta.js'; import { PluginBuilder, PluginFs } from '../Plugin.js'; import { cssPartsToMarkdown } from './converters/markdown-css-parts.js'; import { cssPropsToMarkdown } from './converters/markdown-css-props.js'; import { depsToMarkdown } from './converters/markdown-deps.js'; import { eventsToMarkdown } from './converters/markdown-events.js'; import { examplesToMarkdown } from './converters/markdown-examples.js'; import { methodsToMarkdown } from './converters/markdown-methods.js'; import { propsToMarkdown } from './converters/markdown-props.js'; import { slotsToMarkdown } from './converters/markdown-slots.js'; export interface MarkdownPluginConfig extends Record { outputPath( component: ComponentMeta, fs: PluginFs, ): Promise; transformContent(component: ComponentMeta, content: string): Promise; } export const MARKDOWN_PLUGIN_DEFAULT_CONFIG: MarkdownPluginConfig = { async outputPath(component, fs) { return fs.resolvePath(component.source.dirPath, 'README.md'); }, async transformContent(_, content) { return content; }, }; export async function normalizeMarkdownPluginConfig( config: Partial, ): Promise { return { ...MARKDOWN_PLUGIN_DEFAULT_CONFIG, ...config, }; } /** * Transforms component metadata into markdown (`.md`) files. By default each markdown file will be * output in the directory the component was declared in, feel free to change this * via the config options. This will run in the `transform` plugin lifecycle step. * * @option outputPath - Receives each `ComponentMeta` and it should return the path to where it's * respective markdown file should be output. * @option transformContent - Receives each `ComponentMeta` and the new markdown content as a * `string` and returns the transformed content. * * @example * ```ts * // FILE: celement.config.ts * * import { markdownPlugin } from '@celement/cli'; * * export default [ * markdownPlugin({ * // Configuration options here. * async outputPath(component, fs) { * return fs.resolvePath(component.source.dirPath, 'README.md'); * }, * }), * ]; * ``` */ export const markdownPlugin: PluginBuilder> = ( config = {}, ) => ({ name: '@celement/markdown', async transform(components, fs) { const normalizedConfig = await normalizeMarkdownPluginConfig(config); await Promise.all( components.map(async component => { let targetPath = await normalizedConfig.outputPath(component, fs); if (isString(targetPath)) { targetPath = [targetPath]; } return targetPath.map(async path => { await updateMarkdown( path, serializeDefaultComponentDoc(component), async userContent => normalizedConfig.transformContent( component, serializeComponentDoc(userContent, component, components), ), fs, ); }); }), ); }, }); async function updateMarkdown( targetPath: string, defaultMarkdown: string, serializeNewContent: (userContent: string) => Promise, fs: PluginFs, ) { const isUpdate = await fs.pathExists(targetPath); const content = !isUpdate ? defaultMarkdown : (await fs.readFile(targetPath)).toString(); const userContent = isUpdate ? getUserContent(content) : content; const newContent = await serializeNewContent(userContent); const hasContentChanged = !isUpdate || content !== newContent; if (!hasContentChanged) return; if (!isUpdate) await fs.ensureFile(targetPath); await fs.writeFile(targetPath, newContent); } const AUTO_GEN_COMMENT = ''; const getUserContent = (content: string) => content.substr(0, content.indexOf(`\n${AUTO_GEN_COMMENT}`)); const serializeDefaultComponentDoc = (component: ComponentMeta) => [`# ${component.tagName}`, '', component.documentation, ''].join('\n'); const serializeComponentDoc = ( userContent: string, component: ComponentMeta, components: ComponentMeta[], ) => [ userContent, AUTO_GEN_COMMENT, '', ...examplesToMarkdown(component.docTags), ...propsToMarkdown(component.props), ...methodsToMarkdown(component.methods), ...eventsToMarkdown(component.events), ...slotsToMarkdown(component.slots), ...cssPropsToMarkdown(component.cssProps), ...cssPartsToMarkdown(component.cssParts), ...depsToMarkdown(component, components), '', ].join('\n');