/// import { join, basename } from 'node:path' import { readdir, lstat, writeFile } from 'node:fs/promises' import type { MetaCheckerOptions, EventMeta, PropertyMeta, ExposeMeta, SlotMeta, } from 'vue-component-meta' import { createCheckerByJson } from 'vue-component-meta' const checkerOptions: MetaCheckerOptions = { forceUseTs: true, schema: false, printer: { newLine: 1 }, } type Checker = ReturnType async function main() { const root = process.cwd() const tsconfig = join(root, './tsconfig.json') const componentDir = join(root, './src/components/') const out = join(root, './src/data/documentation/components-meta.ts') const checker = createCheckerByJson(root, { extends: tsconfig, }, checkerOptions) const components: Record = {} const directories = ['base', 'base-addons', 'layouts'] await Promise.all( directories.map(dir => walk(components, checker, join(componentDir, dir))), ) const sorted: Record = {} Object.keys(components).sort().forEach((key) => { sorted[key] = components[key] }) const content = [ `/* eslint-disable */`, `// This file is auto generated by scripts/generate-component-meta.ts`, `import type { ComponentMeta } from 'vue-component-meta'`, ``, `export const components = ${JSON.stringify( Object.keys(sorted), )} as const`, ``, ...Object.entries(sorted).map(([name, meta]) => { return `export const ${name}Meta: ComponentMeta = ${JSON.stringify( meta, )} as const` }), ].join('\n') await writeFile(out, content) } async function walk( components: Record, checker: Checker, dir: string, ) { const files = await readdir(dir) for (const file of files) { const path = join(dir, file) const stat = await lstat(path) if (stat.isDirectory()) { await walk(components, checker, path) // recursive } const isVueFile = path.endsWith('.vue') if (!isVueFile) continue const name = basename(path, '.vue') const meta = await extractMeta(checker, path) components[name] = meta } } async function extractMeta(checker: Checker, path: string) { const metaFields = checker.getComponentMeta(path) const meta = { type: metaFields.type, props: metaFields.props.filter(field => !field.global), events: metaFields.events, exposed: metaFields.exposed.filter((field) => { const isProps = metaFields.props?.findIndex(prop => prop.name === field.name) >= 0 const isEvent = metaFields.events?.findIndex( (event: any) => `on${event.name}`.toLowerCase() === field.name?.toLowerCase(), ) >= 0 const isExcluded = field.name?.startsWith('$') const isModel = field.name === 'modelValue' return !(isProps || isEvent || isExcluded || isModel) }), slots: metaFields.slots, } meta.props.forEach(field => cleanMeta(field)) meta.events.forEach(field => cleanMeta(field)) meta.slots.forEach(field => cleanMeta(field)) meta.exposed.forEach(field => cleanMeta(field)) meta.props.sort((a, b) => { // sort required properties first if (!a.required && b.required) { return 1 } if (a.required && !b.required) { return -1 } // then ensure boolean properties are sorted last if (a.type === 'boolean' && b.type !== 'boolean') { return 1 } if (a.type !== 'boolean' && b.type === 'boolean') { return -1 } return a.name.localeCompare(b.name) }) meta.events.sort((a, b) => a.name.localeCompare(b.name)) meta.slots.sort((a, b) => a.name.localeCompare(b.name)) meta.exposed.sort((a, b) => a.name.localeCompare(b.name)) return meta } function cleanMeta(field: EventMeta | PropertyMeta | ExposeMeta | SlotMeta) { if ('schema' in field) { // @ts-expect-error can't be undefined delete field.schema if ('signature' in field) { field.schema = [] } else { field.schema = '' } } if ('declarations' in field) { // @ts-expect-error can't be undefined delete field.declarations field.declarations = [] } } main().catch(console.error)