Files
oa/scripts/generate-component-meta.ts
2025-05-24 01:49:48 +09:00

158 lines
4.1 KiB
TypeScript

/// <reference types="node" />
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<typeof createCheckerByJson>
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<string, any> = {}
const directories = ['base', 'base-addons', 'layouts']
await Promise.all(
directories.map(dir => walk(components, checker, join(componentDir, dir))),
)
const sorted: Record<string, any> = {}
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<string, any>,
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)