import { join, parse, resolve } from 'node:path' import { existsSync, mkdirSync, writeFileSync } from 'node:fs' import { fileURLToPath } from 'node:url' import fg from 'fast-glob' import MarkdownIt from 'markdown-it' import type { ComponentMeta, MetaCheckerOptions, PropertyMeta, PropertyMetaSchema } from 'vue-component-meta' import { createComponentMetaChecker } from 'vue-component-meta' const __dirname = fileURLToPath(new URL('.', import.meta.url)) const md = new MarkdownIt() const ROOTPATH = '../' const OUTPUTPATH = '../src/content/meta' const checkerOptions: MetaCheckerOptions = { forceUseTs: true, printer: { newLine: 1 }, } const tsconfigChecker = createComponentMetaChecker( resolve(__dirname, ROOTPATH, 'tsconfig.registry.json'), checkerOptions, ) const components = fg.sync(['chart/**/*.vue', 'chart*/**/*.vue'], { cwd: resolve(__dirname, ROOTPATH, 'src/lib/registry/default/ui/'), absolute: true, }) components.forEach((componentPath) => { try { const componentName = parse(componentPath).name const meta = parseMeta(tsconfigChecker.getComponentMeta(componentPath)) const metaDirPath = resolve(__dirname, OUTPUTPATH) // if meta dir doesn't exist create if (!existsSync(metaDirPath)) mkdirSync(metaDirPath) const metaMdFilePath = join(metaDirPath, `${componentName}.md`) let parsedString = '\n\n' if (meta.props.length) parsedString += `\n` if (meta.events.length) parsedString += `\n\n` if (meta.slots.length) parsedString += `\n\n` if (meta.methods.length) parsedString += `\n\n` writeFileSync(metaMdFilePath, parsedString) } catch (err) { console.log(err) } }) function parseTypeFromSchema(schema: PropertyMetaSchema): string { if (typeof schema === 'object' && (schema.kind === 'enum' || schema.kind === 'array')) { const isFlatEnum = schema.schema?.every(val => typeof val === 'string') const enumValue = schema?.schema?.filter(i => i !== 'undefined') ?? [] if (isFlatEnum && /^[A-Z]/.test(schema.type)) return enumValue.join(' | ') else if (typeof schema.schema?.[0] === 'object' && schema.schema?.[0].kind === 'enum') return schema.schema.map((s: PropertyMetaSchema) => parseTypeFromSchema(s)).join(' | ') else return schema.type } else if (typeof schema === 'object' && schema.kind === 'object') { return schema.type } else if (typeof schema === 'string') { return schema } else { return '' } } // Utilities function parseMeta(meta: ComponentMeta) { const props = meta.props // Exclude global props .filter(prop => !prop.global) .map((prop) => { let defaultValue = prop.default let type = prop.type const { name, description, required } = prop if (name === 'as') defaultValue = defaultValue ?? '"div"' if (defaultValue === 'undefined') defaultValue = undefined if (!type.includes('AcceptableValue')) type = parseTypeFromSchema(prop.schema) || type return ({ name, description: md.render(description), type: type.replace(/\s*\|\s*undefined/g, ''), required, default: defaultValue ?? undefined, }) }) const events = meta.events .map((event) => { const { name, type } = event return ({ name, type: type.replace(/\s*\|\s*undefined/g, ''), }) }) const defaultSlot = meta.slots?.[0] const slots: { name: string, description: string, type: string }[] = [] if (defaultSlot && defaultSlot.type !== '{}') { const schema = defaultSlot.schema if (typeof schema === 'object' && schema.schema) { Object.values(schema.schema).forEach((childMeta: PropertyMeta) => { slots.push({ name: childMeta.name, description: md.render(childMeta.description), type: parseTypeFromSchema(childMeta.schema), }) }) } } // exposed method const methods = meta.exposed .filter(expose => typeof expose.schema === 'object' && expose.schema.kind === 'event') .map(expose => ({ name: expose.name, description: md.render(expose.description), type: expose.type, })) return { props, events, slots, methods, } }