-
Notifications
You must be signed in to change notification settings - Fork 713
/
Copy pathmeta.ts
148 lines (126 loc) · 5.38 KB
/
meta.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
import type { ViteDevServer } from 'vite'
import { kebabCase, camelCase } from 'scule'
import defu from 'defu'
import fs from 'node:fs'
import type { Resolver } from '@nuxt/kit'
import type { ComponentMeta } from 'vue-component-meta'
import type { DevtoolsMeta } from '../runtime/composables/extendDevtoolsMeta'
import type { ModuleOptions } from '../module'
export type Component = {
slug: string
label: string
meta?: ComponentMeta & { devtools: DevtoolsMeta<any> }
defaultVariants: Record<string, any>
}
const devtoolsComponentMeta: Record<string, any> = {}
function extractDevtoolsMeta(code: string): string | null {
const match = code.match(/extendDevtoolsMeta(?:<.*?>)?\(/)
if (!match) return null
const startIndex = code.indexOf(match[0]) + match[0].length
let openBraceCount = 0
let closeBraceCount = 0
let endIndex = startIndex
for (let i = startIndex; i < code.length; i++) {
if (code[i] === '{') openBraceCount++
if (code[i] === '}') closeBraceCount++
if (openBraceCount > 0 && openBraceCount === closeBraceCount) {
endIndex = i + 1
break
}
}
// Return only the object inside extendDevtoolsMeta
return code.slice(startIndex, endIndex).trim()
}
// A Plugin to parse additional metadata for the Nuxt UI Devtools.
export function devtoolsMetaPlugin({ resolve, options, templates }: { resolve: Resolver['resolve'], options: ModuleOptions, templates: Record<string, any> }) {
return {
name: 'ui-devtools-component-meta',
enforce: 'pre' as const,
async transform(code: string, id: string) {
if (!id.match(/\/runtime\/components\/\w+.vue/)) return
const fileName = id.split('/')[id.split('/').length - 1]
if (code && fileName) {
const slug = kebabCase(fileName.replace(/\..*/, ''))
const match = extractDevtoolsMeta(code)
if (match) {
const metaObject = new Function(`return ${match}`)()
devtoolsComponentMeta[slug] = { meta: { devtools: { ...metaObject } } }
}
}
return {
code
}
},
configureServer(server: ViteDevServer) {
server.middlewares.use('/__nuxt_ui__/devtools/api/component-meta', async (_req, res) => {
res.setHeader('Content-Type', 'application/json')
try {
const componentMeta = await import('./.component-meta/component-meta')
const meta = defu(
Object.entries(componentMeta.default).reduce((acc, [key, value]: [string, any]) => {
if (!key.startsWith('U')) return acc
const name = key.substring(1)
const slug = kebabCase(name)
const template = templates?.[camelCase(name)]
if (devtoolsComponentMeta[slug] === undefined) {
const path = resolve(`./runtime/components/${name}.vue`)
const code = fs.readFileSync(path, 'utf-8')
const match = extractDevtoolsMeta(code)
if (match) {
const metaObject = new Function(`return ${match}`)()
devtoolsComponentMeta[slug] = { meta: { devtools: { ...metaObject } } }
} else {
devtoolsComponentMeta[slug] = null
}
}
value.meta.props = value.meta.props.map((prop: any) => {
let defaultValue = prop.default
? prop.default
: prop?.tags?.find((tag: any) =>
tag.name === 'defaultValue'
&& !tag.text?.includes('appConfig'))?.text
?? template?.defaultVariants?.[prop.name]
if (typeof defaultValue === 'string') defaultValue = defaultValue?.replaceAll(/["'`]/g, '')
if (defaultValue === 'true') defaultValue = true
if (defaultValue === 'false') defaultValue = false
if (!Number.isNaN(Number.parseInt(defaultValue))) defaultValue = Number.parseInt(defaultValue)
return {
...prop,
default: defaultValue
}
})
const label = key.replace(/^U/, options.prefix ?? 'U')
acc[kebabCase(key.replace(/^U/, ''))] = { ...value, label, slug }
return acc
}, {} as Record<string, any>),
devtoolsComponentMeta
)
res.end(JSON.stringify(meta))
} catch (error) {
console.error(`Failed to fetch component meta`, error)
res.statusCode = 500
res.end(JSON.stringify({ error: 'Failed to fetch component meta' }))
}
})
server.middlewares.use('/__nuxt_ui__/devtools/api/component-example', async (req, res) => {
const query = new URL(req.url!, 'http://localhost').searchParams
const name = query.get('component')
if (!name) {
res.statusCode = 400
res.end(JSON.stringify({ error: 'Component name is required' }))
return
}
try {
const path = resolve(`./devtools/runtime/examples/${name}.vue`)
const source = fs.readFileSync(path, 'utf-8')
res.setHeader('Content-Type', 'application/json')
res.end(JSON.stringify({ component: name, source }))
} catch (error) {
console.error(`Failed to read component source for ${name}:`, error)
res.statusCode = 500
res.end(JSON.stringify({ error: 'Failed to read component source' }))
}
})
}
}
}