@rimelight/ui 0.0.23 → 0.0.25
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +8 -3
- package/src/components/Head.astro +199 -156
- package/src/components/astro/RLAAccordion.astro +22 -0
- package/src/components/astro/RLAButton.astro +104 -85
- package/src/components/astro/RLAFooter.astro +14 -40
- package/src/components/astro/RLAHeader.astro +18 -78
- package/src/components/astro/RLAIcon.astro +11 -36
- package/src/components/astro/RLALogo.astro +9 -44
- package/src/components/astro/RLAPlaceholder.astro +8 -33
- package/src/components/astro/RLAQuote.astro +15 -0
- package/src/components/astro/RLAScrollToTop.astro +171 -221
- package/src/components/astro/RLASection.astro +181 -0
- package/src/components/vue/RLVButton.vue +125 -50
- package/src/components/vue/RLVFooter.vue +12 -49
- package/src/components/vue/RLVHeader.vue +15 -98
- package/src/components/vue/RLVIcon.vue +11 -37
- package/src/components/vue/RLVLogo.vue +8 -56
- package/src/components/vue/RLVPlaceholder.vue +11 -33
- package/src/components/vue/RLVScrollToTop.vue +17 -88
- package/src/components/vue/RLVSection.vue +98 -0
- package/src/composables/index.ts +0 -1
- package/src/env.d.ts +5 -0
- package/src/integrations/ui.ts +2 -6
- package/src/nuxt-types.ts +3 -0
- package/src/plugins/index.ts +5 -2
- package/src/plugins/starlightAddons/index.ts +3 -64
- package/src/plugins/starlightDocsApi/components/ApiEmits.astro +46 -0
- package/src/plugins/starlightDocsApi/components/ApiProps.astro +47 -0
- package/src/plugins/starlightDocsApi/components/ApiSlots.astro +50 -0
- package/src/plugins/starlightDocsApi/components/ApiTheme.astro +27 -0
- package/src/plugins/starlightDocsApi/extract-vue.ts +286 -0
- package/src/plugins/starlightDocsApi/index.ts +265 -0
- package/src/plugins/starlightDocsApi/types.ts +40 -0
- package/src/plugins/starlightDocsApi/utils.ts +45 -0
- package/src/themes/button.theme.ts +179 -23
- package/src/themes/footer.theme.ts +4 -6
- package/src/themes/header.theme.ts +4 -28
- package/src/themes/icon.theme.ts +4 -2
- package/src/themes/index.ts +15 -20
- package/src/themes/logo.theme.ts +4 -2
- package/src/themes/placeholder.theme.ts +4 -6
- package/src/themes/scroll-to-top.theme.ts +4 -6
- package/src/themes/section.theme.ts +185 -0
- package/src/types/componentVariant.ts +10 -0
- package/src/types/index.ts +1 -0
- package/src/utils/defineTheme.ts +11 -0
- package/src/utils/index.ts +3 -0
- package/src/utils/resolveClasses.ts +21 -0
- package/src/utils/useUi.ts +19 -0
- package/src/composables/useUi.ts +0 -27
- package/src/plugins/starlightAddons/Sidebar.astro +0 -89
- package/src/plugins/starlightAddons/data.ts +0 -39
- package/src/plugins/starlightAddons/libs/config.ts +0 -107
- package/src/plugins/starlightAddons/libs/content.ts +0 -20
- package/src/plugins/starlightAddons/libs/i18n.ts +0 -51
- package/src/plugins/starlightAddons/libs/locals.ts +0 -12
- package/src/plugins/starlightAddons/libs/pathname.ts +0 -22
- package/src/plugins/starlightAddons/libs/plugin.ts +0 -9
- package/src/plugins/starlightAddons/libs/sidebar.ts +0 -183
- package/src/plugins/starlightAddons/libs/vite.ts +0 -46
- package/src/plugins/starlightAddons/locals.d.ts +0 -14
- package/src/plugins/starlightAddons/middleware.ts +0 -132
- package/src/plugins/starlightAddons/overrides/Sidebar.astro +0 -8
- package/src/plugins/starlightAddons/schema.ts +0 -13
- package/src/plugins/starlightAddons/virtual.d.ts +0 -13
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs"
|
|
2
|
+
import ts from "typescript"
|
|
3
|
+
import type { PropMeta, SlotMeta, EmitMeta } from "./types"
|
|
4
|
+
|
|
5
|
+
function extractScriptBlock(content: string): string | null {
|
|
6
|
+
const match =
|
|
7
|
+
content.match(/<script[^>]*lang=["']ts["'][^>]*>([\s\S]*?)<\/script>/) ||
|
|
8
|
+
content.match(/<script[^>]*>([\s\S]*?)<\/script>/)
|
|
9
|
+
return match?.[1] ?? null
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function extractTemplateBlock(content: string): string {
|
|
13
|
+
const match = content.match(/<template[^>]*>([\s\S]*?)<\/template>/)
|
|
14
|
+
return match?.[1] ?? ""
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function getJSDocDescription(node: ts.Node): string {
|
|
18
|
+
const jsDocs = ts.getJSDocCommentsAndTags(node)
|
|
19
|
+
if (jsDocs.length === 0) return ""
|
|
20
|
+
|
|
21
|
+
const comment = jsDocs[0]
|
|
22
|
+
if (comment && ts.isJSDoc(comment)) {
|
|
23
|
+
const text = comment.comment
|
|
24
|
+
if (typeof text === "string") return text.trim()
|
|
25
|
+
if (Array.isArray(text))
|
|
26
|
+
return text
|
|
27
|
+
.map((t) => (typeof t === "string" ? t : t.text))
|
|
28
|
+
.join(" ")
|
|
29
|
+
.trim()
|
|
30
|
+
}
|
|
31
|
+
return ""
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function extractPropsFromInterface(node: ts.InterfaceDeclaration): PropMeta[] {
|
|
35
|
+
const props: PropMeta[] = []
|
|
36
|
+
for (const member of node.members) {
|
|
37
|
+
if (ts.isPropertySignature(member) && member.name && ts.isIdentifier(member.name)) {
|
|
38
|
+
const propName = member.name.text
|
|
39
|
+
if (propName === "class" || propName.startsWith("data-")) continue
|
|
40
|
+
const type = member.type ? member.type.getText().replace(/^[^:]+:\s*/, "") : "unknown"
|
|
41
|
+
const isOptional = member.questionToken !== undefined
|
|
42
|
+
const description = getJSDocDescription(member)
|
|
43
|
+
props.push({ name: propName, type, required: !isOptional, description })
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return props
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function extractPropsFromTypeLiteral(
|
|
50
|
+
node: ts.TypeLiteralNode,
|
|
51
|
+
defaults?: Record<string, string>
|
|
52
|
+
): PropMeta[] {
|
|
53
|
+
const props: PropMeta[] = []
|
|
54
|
+
for (const member of node.members) {
|
|
55
|
+
if (ts.isPropertySignature(member) && member.name && ts.isIdentifier(member.name)) {
|
|
56
|
+
const propName = member.name.text
|
|
57
|
+
if (propName === "class" || propName.startsWith("data-")) continue
|
|
58
|
+
const type = member.type ? member.type.getText().replace(/^[^:]+:\s*/, "") : "unknown"
|
|
59
|
+
const isOptional = member.questionToken !== undefined
|
|
60
|
+
const defaultVal = defaults?.[propName]
|
|
61
|
+
const prop: PropMeta = { name: propName, type, required: !isOptional, description: "" }
|
|
62
|
+
if (defaultVal !== undefined) prop.default = defaultVal
|
|
63
|
+
props.push(prop)
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
return props
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function getTypeRefName(typeArg: ts.TypeNode): string | null {
|
|
70
|
+
if (ts.isTypeReferenceNode(typeArg) && ts.isIdentifier(typeArg.typeName)) {
|
|
71
|
+
return typeArg.typeName.text
|
|
72
|
+
}
|
|
73
|
+
return null
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export function extractVueMetaDirect(filePath: string): {
|
|
77
|
+
props: PropMeta[]
|
|
78
|
+
slots: SlotMeta[]
|
|
79
|
+
emits: EmitMeta[]
|
|
80
|
+
} {
|
|
81
|
+
const content = readFileSync(filePath, "utf-8")
|
|
82
|
+
const scriptContent = extractScriptBlock(content)
|
|
83
|
+
|
|
84
|
+
const props: PropMeta[] = []
|
|
85
|
+
const slots: SlotMeta[] = []
|
|
86
|
+
const emits: EmitMeta[] = []
|
|
87
|
+
|
|
88
|
+
if (scriptContent) {
|
|
89
|
+
const sourceFile = ts.createSourceFile(
|
|
90
|
+
"component.ts",
|
|
91
|
+
scriptContent,
|
|
92
|
+
ts.ScriptTarget.Latest,
|
|
93
|
+
true,
|
|
94
|
+
ts.ScriptKind.TS
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
const interfaces = new Map<string, ts.InterfaceDeclaration>()
|
|
98
|
+
const destructuredDefaults: Record<string, string> = {}
|
|
99
|
+
|
|
100
|
+
function collectDeclarations(node: ts.Node) {
|
|
101
|
+
if (ts.isInterfaceDeclaration(node)) {
|
|
102
|
+
interfaces.set(node.name.text, node)
|
|
103
|
+
}
|
|
104
|
+
if (ts.isVariableStatement(node)) {
|
|
105
|
+
for (const decl of node.declarationList.declarations) {
|
|
106
|
+
if (
|
|
107
|
+
decl.initializer &&
|
|
108
|
+
ts.isCallExpression(decl.initializer) &&
|
|
109
|
+
ts.isObjectBindingPattern(decl.name)
|
|
110
|
+
) {
|
|
111
|
+
const expr = decl.initializer.expression
|
|
112
|
+
const fnName = ts.isIdentifier(expr) ? expr.text : ""
|
|
113
|
+
if (fnName === "defineProps") {
|
|
114
|
+
for (const element of decl.name.elements) {
|
|
115
|
+
if (
|
|
116
|
+
ts.isBindingElement(element) &&
|
|
117
|
+
ts.isIdentifier(element.name) &&
|
|
118
|
+
element.initializer
|
|
119
|
+
) {
|
|
120
|
+
destructuredDefaults[element.name.text] = element.initializer.getText()
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
ts.forEachChild(node, collectDeclarations)
|
|
128
|
+
}
|
|
129
|
+
collectDeclarations(sourceFile)
|
|
130
|
+
|
|
131
|
+
function visit(node: ts.Node) {
|
|
132
|
+
if (ts.isCallExpression(node)) {
|
|
133
|
+
const expr = node.expression
|
|
134
|
+
const fnName = ts.isIdentifier(expr) ? expr.text : ""
|
|
135
|
+
|
|
136
|
+
if (fnName === "defineProps") {
|
|
137
|
+
if (node.typeArguments && node.typeArguments.length > 0) {
|
|
138
|
+
const typeArg = node.typeArguments[0]!
|
|
139
|
+
const refName = getTypeRefName(typeArg)
|
|
140
|
+
if (refName) {
|
|
141
|
+
const iface = interfaces.get(refName)
|
|
142
|
+
if (iface) {
|
|
143
|
+
const extracted = extractPropsFromInterface(iface)
|
|
144
|
+
for (const p of extracted) {
|
|
145
|
+
const d = destructuredDefaults[p.name]
|
|
146
|
+
if (d !== undefined) p.default = d
|
|
147
|
+
}
|
|
148
|
+
props.push(...extracted)
|
|
149
|
+
}
|
|
150
|
+
} else if (ts.isTypeLiteralNode(typeArg)) {
|
|
151
|
+
props.push(...extractPropsFromTypeLiteral(typeArg))
|
|
152
|
+
}
|
|
153
|
+
} else if (node.arguments.length > 0) {
|
|
154
|
+
const arg = node.arguments[0]!
|
|
155
|
+
if (ts.isTypeLiteralNode(arg)) {
|
|
156
|
+
props.push(...extractPropsFromTypeLiteral(arg))
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
if (fnName === "withDefaults" && node.arguments.length > 0) {
|
|
162
|
+
const callArg = node.arguments[0]!
|
|
163
|
+
if (ts.isCallExpression(callArg)) {
|
|
164
|
+
const innerFn = ts.isIdentifier(callArg.expression) ? callArg.expression.text : ""
|
|
165
|
+
if (innerFn === "defineProps") {
|
|
166
|
+
const defaultsObj = node.arguments[1]!
|
|
167
|
+
const defaults: Record<string, string> = {}
|
|
168
|
+
if (ts.isObjectLiteralExpression(defaultsObj)) {
|
|
169
|
+
for (const prop of defaultsObj.properties) {
|
|
170
|
+
if (ts.isPropertyAssignment(prop) && ts.isIdentifier(prop.name)) {
|
|
171
|
+
defaults[prop.name.text] = prop.initializer.getText()
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
if (callArg.typeArguments && callArg.typeArguments.length > 0) {
|
|
177
|
+
const typeArg = callArg.typeArguments[0]!
|
|
178
|
+
const refName = getTypeRefName(typeArg)
|
|
179
|
+
if (refName) {
|
|
180
|
+
const iface = interfaces.get(refName)
|
|
181
|
+
if (iface) {
|
|
182
|
+
const baseProps = extractPropsFromInterface(iface)
|
|
183
|
+
for (const p of baseProps) {
|
|
184
|
+
const d = defaults[p.name]
|
|
185
|
+
if (d !== undefined) p.default = d
|
|
186
|
+
}
|
|
187
|
+
props.push(...baseProps)
|
|
188
|
+
}
|
|
189
|
+
} else if (ts.isTypeLiteralNode(typeArg)) {
|
|
190
|
+
props.push(...extractPropsFromTypeLiteral(typeArg, defaults))
|
|
191
|
+
}
|
|
192
|
+
} else if (callArg.arguments.length > 0) {
|
|
193
|
+
const arg = callArg.arguments[0]!
|
|
194
|
+
if (ts.isTypeLiteralNode(arg)) {
|
|
195
|
+
props.push(...extractPropsFromTypeLiteral(arg, defaults))
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
if (fnName === "defineSlots") {
|
|
203
|
+
const typeArg = node.typeArguments?.[0]
|
|
204
|
+
if (typeArg && ts.isTypeLiteralNode(typeArg)) {
|
|
205
|
+
for (const member of typeArg.members) {
|
|
206
|
+
if (ts.isMethodSignature(member) && member.name && ts.isIdentifier(member.name)) {
|
|
207
|
+
const slotName = member.name.text
|
|
208
|
+
const bindings: Record<string, string> = {}
|
|
209
|
+
if (member.parameters.length > 0) {
|
|
210
|
+
const param = member.parameters[0]
|
|
211
|
+
if (param?.type) {
|
|
212
|
+
const typeText = param.type.getText()
|
|
213
|
+
bindings["props"] = typeText
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
slots.push({ name: slotName, bindings, description: "" })
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
if (fnName === "defineEmits") {
|
|
223
|
+
const typeArg = node.typeArguments?.[0]
|
|
224
|
+
const runtimeArg = node.arguments?.[0]
|
|
225
|
+
|
|
226
|
+
if (typeArg && ts.isTypeLiteralNode(typeArg)) {
|
|
227
|
+
for (const member of typeArg.members) {
|
|
228
|
+
if (
|
|
229
|
+
ts.isPropertySignature(member) &&
|
|
230
|
+
member.name &&
|
|
231
|
+
ts.isIdentifier(member.name) &&
|
|
232
|
+
member.type
|
|
233
|
+
) {
|
|
234
|
+
const emitName = member.name.text
|
|
235
|
+
let payloadType = "void"
|
|
236
|
+
if (ts.isFunctionTypeNode(member.type) && member.type.parameters.length > 0) {
|
|
237
|
+
const param = member.type.parameters[0]
|
|
238
|
+
if (param?.type) {
|
|
239
|
+
payloadType = param.type.getText()
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
emits.push({ name: emitName, type: payloadType, description: "" })
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
} else if (runtimeArg && ts.isTypeLiteralNode(runtimeArg)) {
|
|
246
|
+
for (const member of runtimeArg.members) {
|
|
247
|
+
if (
|
|
248
|
+
ts.isPropertySignature(member) &&
|
|
249
|
+
member.name &&
|
|
250
|
+
ts.isIdentifier(member.name) &&
|
|
251
|
+
member.type
|
|
252
|
+
) {
|
|
253
|
+
const emitName = member.name.text
|
|
254
|
+
let payloadType = "void"
|
|
255
|
+
if (ts.isFunctionTypeNode(member.type) && member.type.parameters.length > 0) {
|
|
256
|
+
const param = member.type.parameters[0]
|
|
257
|
+
if (param?.type) {
|
|
258
|
+
payloadType = param.type.getText()
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
emits.push({ name: emitName, type: payloadType, description: "" })
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
ts.forEachChild(node, visit)
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
visit(sourceFile)
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
const templateContent = extractTemplateBlock(content)
|
|
274
|
+
const slotMatches = templateContent.matchAll(/<slot(?:\s+name=["']([^"']+)["'])?/g)
|
|
275
|
+
for (const match of slotMatches) {
|
|
276
|
+
const slotName = match[1] ?? "default"
|
|
277
|
+
if (!slots.some((s) => s.name === slotName)) {
|
|
278
|
+
slots.push({ name: slotName, bindings: {}, description: "" })
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
if (!slots.length) {
|
|
282
|
+
slots.push({ name: "default", bindings: {}, description: "" })
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
return { props, slots, emits }
|
|
286
|
+
}
|
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
import type { AstroIntegration } from "astro"
|
|
2
|
+
import virtual from "vite-plugin-virtual"
|
|
3
|
+
import path from "node:path"
|
|
4
|
+
import { fileURLToPath } from "node:url"
|
|
5
|
+
import { readFileSync, existsSync, readdirSync } from "node:fs"
|
|
6
|
+
import ts from "typescript"
|
|
7
|
+
import type { ComponentMeta, PropMeta, SlotMeta, EmitMeta, ThemeMeta } from "./types"
|
|
8
|
+
import { extractVueMetaDirect } from "./extract-vue"
|
|
9
|
+
|
|
10
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
|
11
|
+
const uiRoot = path.resolve(__dirname, "..", "..", "..")
|
|
12
|
+
|
|
13
|
+
function pascalToKebab(name: string): string {
|
|
14
|
+
return name.replace(/([a-z0-9])([A-Z])/g, "$1-$2").toLowerCase()
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function extractAstroPropsFromInterface(sourceFile: ts.SourceFile): PropMeta[] {
|
|
18
|
+
const props: PropMeta[] = []
|
|
19
|
+
|
|
20
|
+
function visit(node: ts.Node) {
|
|
21
|
+
if (
|
|
22
|
+
(ts.isInterfaceDeclaration(node) || ts.isTypeAliasDeclaration(node)) &&
|
|
23
|
+
node.modifiers?.some((m) => m.kind === ts.SyntaxKind.ExportKeyword)
|
|
24
|
+
) {
|
|
25
|
+
const name = node.name.text
|
|
26
|
+
if (name.endsWith("Props") || name === "Props") {
|
|
27
|
+
if (ts.isInterfaceDeclaration(node)) {
|
|
28
|
+
for (const member of node.members) {
|
|
29
|
+
if (ts.isPropertySignature(member) && member.name && ts.isIdentifier(member.name)) {
|
|
30
|
+
const propName = member.name.text
|
|
31
|
+
if (propName === "class" || propName.startsWith("data-")) continue
|
|
32
|
+
const type = member.type ? member.type.getText().replace(/^[^:]+:\s*/, "") : "unknown"
|
|
33
|
+
const isOptional = member.questionToken !== undefined
|
|
34
|
+
props.push({ name: propName, type, required: !isOptional, description: "" })
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
} else if (ts.isTypeAliasDeclaration(node) && ts.isTypeLiteralNode(node.type)) {
|
|
38
|
+
for (const member of node.type.members) {
|
|
39
|
+
if (ts.isPropertySignature(member) && member.name && ts.isIdentifier(member.name)) {
|
|
40
|
+
const propName = member.name.text
|
|
41
|
+
if (propName === "class" || propName.startsWith("data-")) continue
|
|
42
|
+
const type = member.type ? member.type.getText().replace(/^[^:]+:\s*/, "") : "unknown"
|
|
43
|
+
const isOptional = member.questionToken !== undefined
|
|
44
|
+
props.push({ name: propName, type, required: !isOptional, description: "" })
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
ts.forEachChild(node, visit)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
visit(sourceFile)
|
|
54
|
+
return props
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function extractAstroMeta(filePath: string): { props: PropMeta[]; slots: SlotMeta[] } {
|
|
58
|
+
const content = readFileSync(filePath, "utf-8")
|
|
59
|
+
const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---/)
|
|
60
|
+
if (!frontmatterMatch) return { props: [], slots: [] }
|
|
61
|
+
|
|
62
|
+
const frontmatter = frontmatterMatch[1]
|
|
63
|
+
const sourceFile = ts.createSourceFile(
|
|
64
|
+
"component.ts",
|
|
65
|
+
frontmatter ?? "",
|
|
66
|
+
ts.ScriptTarget.Latest,
|
|
67
|
+
true,
|
|
68
|
+
ts.ScriptKind.TS
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
const props = extractAstroPropsFromInterface(sourceFile)
|
|
72
|
+
|
|
73
|
+
const slots: SlotMeta[] = []
|
|
74
|
+
const slotMatches = content.matchAll(/<slot(?:\s+name=["']([^"']+)["'])?/g)
|
|
75
|
+
for (const match of slotMatches) {
|
|
76
|
+
slots.push({ name: match[1] ?? "default", bindings: {}, description: "" })
|
|
77
|
+
}
|
|
78
|
+
if (!slots.length) slots.push({ name: "default", bindings: {}, description: "" })
|
|
79
|
+
|
|
80
|
+
return { props, slots }
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function safeParse(code: string): unknown {
|
|
84
|
+
try {
|
|
85
|
+
const normalized = code
|
|
86
|
+
.replace(/'/g, '"')
|
|
87
|
+
.replace(/,\s*\}/g, "}")
|
|
88
|
+
.replace(/,\s*\]/g, "]")
|
|
89
|
+
return JSON.parse(normalized)
|
|
90
|
+
} catch {
|
|
91
|
+
return {}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function isStringOrArray(value: unknown): value is string | string[] {
|
|
96
|
+
return typeof value === "string" || Array.isArray(value)
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function isStringRecord(value: unknown): value is Record<string, string | string[]> {
|
|
100
|
+
if (typeof value !== "object" || value === null) return false
|
|
101
|
+
return Object.values(value).every(isStringOrArray)
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function isVariantRecord(
|
|
105
|
+
value: unknown
|
|
106
|
+
): value is Record<string, Record<string, Record<string, string | string[]>>> {
|
|
107
|
+
if (typeof value !== "object" || value === null) return false
|
|
108
|
+
return Object.values(value).every(
|
|
109
|
+
(v) => typeof v === "object" && v !== null && Object.values(v).every(isStringRecord)
|
|
110
|
+
)
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function isCompoundArray(value: unknown): value is Record<string, unknown>[] {
|
|
114
|
+
return Array.isArray(value) && value.every((v) => typeof v === "object" && v !== null)
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function isDefaultVariants(value: unknown): value is Record<string, unknown> {
|
|
118
|
+
return typeof value === "object" && value !== null
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function extractThemeMeta(componentName: string, themesDir: string): ThemeMeta | null {
|
|
122
|
+
if (!existsSync(themesDir)) return null
|
|
123
|
+
|
|
124
|
+
const themeFiles = readdirSync(themesDir).filter((f) => f.endsWith(".theme.ts"))
|
|
125
|
+
let themePath: string | null = null
|
|
126
|
+
for (const file of themeFiles) {
|
|
127
|
+
const baseName = file.replace(".theme.ts", "")
|
|
128
|
+
if (baseName === componentName.toLowerCase()) {
|
|
129
|
+
themePath = path.join(themesDir, file)
|
|
130
|
+
break
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
if (!themePath || !existsSync(themePath)) return null
|
|
135
|
+
|
|
136
|
+
const content = readFileSync(themePath, "utf-8")
|
|
137
|
+
const slotsMatch = content.match(/slots:\s*\[([^\]]*)\]/)
|
|
138
|
+
const baseMatch = content.match(/base:\s*(\{[\s\S]*?\n[ \t]*\})/)
|
|
139
|
+
const variantsMatch = content.match(/variants:\s*(\{[\s\S]*?\n[ \t]*\})/)
|
|
140
|
+
const compoundMatch = content.match(/compoundVariants:\s*(\[[\s\S]*?\n[ \t]*\])/)
|
|
141
|
+
const defaultVariantsMatch = content.match(/defaultVariants:\s*(\{[\s\S]*?\n[ \t]*\})/)
|
|
142
|
+
|
|
143
|
+
const slots = slotsMatch
|
|
144
|
+
? slotsMatch[1]!
|
|
145
|
+
.split(",")
|
|
146
|
+
.map((s) => s.trim().replace(/["']/g, ""))
|
|
147
|
+
.filter(Boolean)
|
|
148
|
+
: []
|
|
149
|
+
|
|
150
|
+
const parsedBase = baseMatch ? safeParse(baseMatch[1]!) : {}
|
|
151
|
+
const parsedVariants = variantsMatch ? safeParse(variantsMatch[1]!) : {}
|
|
152
|
+
const parsedCompound = compoundMatch ? safeParse(compoundMatch[1]!) : []
|
|
153
|
+
const parsedDefaults = defaultVariantsMatch ? safeParse(defaultVariantsMatch[1]!) : {}
|
|
154
|
+
|
|
155
|
+
return {
|
|
156
|
+
slots,
|
|
157
|
+
base: isStringRecord(parsedBase) ? parsedBase : {},
|
|
158
|
+
variants: isVariantRecord(parsedVariants) ? parsedVariants : {},
|
|
159
|
+
compoundVariants: isCompoundArray(parsedCompound) ? parsedCompound : [],
|
|
160
|
+
defaultVariants: isDefaultVariants(parsedDefaults) ? parsedDefaults : {}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function getComponentNameFromFilename(filename: string): string {
|
|
165
|
+
return filename.replace(/^(RLA|RLV)/, "").replace(/\.(astro|vue)$/, "")
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function extractAllMeta(): Record<string, ComponentMeta> {
|
|
169
|
+
const componentsDir = path.join(uiRoot, "src", "components")
|
|
170
|
+
const themesDir = path.join(uiRoot, "src", "themes")
|
|
171
|
+
|
|
172
|
+
const vueDir = path.join(componentsDir, "vue")
|
|
173
|
+
const astroDir = path.join(componentsDir, "astro")
|
|
174
|
+
|
|
175
|
+
const vueFiles = existsSync(vueDir)
|
|
176
|
+
? readdirSync(vueDir).filter((f: string) => f.endsWith(".vue"))
|
|
177
|
+
: []
|
|
178
|
+
const astroFiles = existsSync(astroDir)
|
|
179
|
+
? readdirSync(astroDir).filter((f: string) => f.endsWith(".astro") && f !== "Head.astro")
|
|
180
|
+
: []
|
|
181
|
+
|
|
182
|
+
const vueComponents = new Map<string, string>()
|
|
183
|
+
const astroComponents = new Map<string, string>()
|
|
184
|
+
|
|
185
|
+
for (const f of vueFiles) vueComponents.set(getComponentNameFromFilename(f), f)
|
|
186
|
+
for (const f of astroFiles) astroComponents.set(getComponentNameFromFilename(f), f)
|
|
187
|
+
|
|
188
|
+
const allNames = new Set([...vueComponents.keys(), ...astroComponents.keys()])
|
|
189
|
+
const result: Record<string, ComponentMeta> = {}
|
|
190
|
+
|
|
191
|
+
for (const name of [...allNames].toSorted()) {
|
|
192
|
+
const vueFile = vueComponents.get(name)
|
|
193
|
+
const astroFile = astroComponents.get(name)
|
|
194
|
+
|
|
195
|
+
let props: PropMeta[] = []
|
|
196
|
+
let slots: SlotMeta[] = []
|
|
197
|
+
let emits: EmitMeta[] = []
|
|
198
|
+
const frameworks: ("astro" | "vue")[] = []
|
|
199
|
+
|
|
200
|
+
if (vueFile) {
|
|
201
|
+
frameworks.push("vue")
|
|
202
|
+
try {
|
|
203
|
+
const vueMeta = extractVueMetaDirect(path.join(vueDir, vueFile))
|
|
204
|
+
props = vueMeta.props
|
|
205
|
+
slots = vueMeta.slots
|
|
206
|
+
emits = vueMeta.emits
|
|
207
|
+
} catch (err) {
|
|
208
|
+
console.warn(`[starlightDocsApi] Failed to extract Vue meta for ${vueFile}:`, err)
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
if (astroFile) {
|
|
213
|
+
frameworks.push("astro")
|
|
214
|
+
if (!vueFile) {
|
|
215
|
+
try {
|
|
216
|
+
const astroMeta = extractAstroMeta(path.join(astroDir, astroFile))
|
|
217
|
+
props = astroMeta.props
|
|
218
|
+
slots = astroMeta.slots
|
|
219
|
+
} catch (err) {
|
|
220
|
+
console.warn(`[starlightDocsApi] Failed to extract Astro meta for ${astroFile}:`, err)
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
const theme = extractThemeMeta(name, themesDir)
|
|
226
|
+
|
|
227
|
+
result[pascalToKebab(name)] = {
|
|
228
|
+
name,
|
|
229
|
+
astroComponent: astroFile ?? null,
|
|
230
|
+
vueComponent: vueFile ?? null,
|
|
231
|
+
frameworks,
|
|
232
|
+
props,
|
|
233
|
+
slots,
|
|
234
|
+
emits,
|
|
235
|
+
theme
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
return result
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
export interface StarlightDocsApiConfig {}
|
|
243
|
+
|
|
244
|
+
export default function starlightDocsApi(_config?: StarlightDocsApiConfig): AstroIntegration {
|
|
245
|
+
return {
|
|
246
|
+
name: "rimelight-ui-starlight-docs-api",
|
|
247
|
+
hooks: {
|
|
248
|
+
"astro:config:setup": ({ updateConfig, logger }) => {
|
|
249
|
+
logger.info("Initializing @rimelight/ui docs API")
|
|
250
|
+
|
|
251
|
+
const meta = extractAllMeta()
|
|
252
|
+
|
|
253
|
+
updateConfig({
|
|
254
|
+
vite: {
|
|
255
|
+
plugins: [
|
|
256
|
+
virtual({
|
|
257
|
+
"virtual:rimelight-ui-docs-meta": `export default ${JSON.stringify(meta)}`
|
|
258
|
+
})
|
|
259
|
+
]
|
|
260
|
+
}
|
|
261
|
+
} as Record<string, unknown>)
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
export interface PropMeta {
|
|
2
|
+
name: string
|
|
3
|
+
type: string
|
|
4
|
+
default?: string
|
|
5
|
+
required: boolean
|
|
6
|
+
description: string
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export interface SlotMeta {
|
|
10
|
+
name: string
|
|
11
|
+
bindings: Record<string, string>
|
|
12
|
+
description: string
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface EmitMeta {
|
|
16
|
+
name: string
|
|
17
|
+
type: string
|
|
18
|
+
description: string
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface ThemeMeta {
|
|
22
|
+
slots: string[]
|
|
23
|
+
base: Record<string, string | string[]>
|
|
24
|
+
variants: Record<string, Record<string, Record<string, string | string[]>>>
|
|
25
|
+
compoundVariants: Record<string, unknown>[]
|
|
26
|
+
defaultVariants: Record<string, unknown>
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface ComponentMeta {
|
|
30
|
+
name: string
|
|
31
|
+
astroComponent: string | null
|
|
32
|
+
vueComponent: string | null
|
|
33
|
+
frameworks: ("astro" | "vue")[]
|
|
34
|
+
props: PropMeta[]
|
|
35
|
+
slots: SlotMeta[]
|
|
36
|
+
emits: EmitMeta[]
|
|
37
|
+
theme: ThemeMeta | null
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export type ComponentMetaMap = Record<string, ComponentMeta>
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import type { PropMeta, ThemeMeta } from "./types"
|
|
2
|
+
|
|
3
|
+
export function kebabToPascal(name: string): string {
|
|
4
|
+
return name
|
|
5
|
+
.split("-")
|
|
6
|
+
.map((segment) => segment.charAt(0).toUpperCase() + segment.slice(1))
|
|
7
|
+
.join("")
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function filterProps(
|
|
11
|
+
props: PropMeta[],
|
|
12
|
+
options: { hide?: string[]; show?: string[] }
|
|
13
|
+
): PropMeta[] {
|
|
14
|
+
if (options.show?.length) {
|
|
15
|
+
return props.filter((p) => options.show!.includes(p.name))
|
|
16
|
+
}
|
|
17
|
+
if (options.hide?.length) {
|
|
18
|
+
return props.filter((p) => !options.hide!.includes(p.name))
|
|
19
|
+
}
|
|
20
|
+
return props
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function generateThemeCode(componentName: string, theme: ThemeMeta): string {
|
|
24
|
+
const componentConfig: Record<string, unknown> = {
|
|
25
|
+
...(theme.slots.length ? { slots: theme.slots } : {}),
|
|
26
|
+
...(Object.keys(theme.base).length ? { base: theme.base } : {}),
|
|
27
|
+
...(Object.keys(theme.variants).length ? { variants: theme.variants } : {}),
|
|
28
|
+
...(theme.compoundVariants.length ? { compoundVariants: theme.compoundVariants } : {}),
|
|
29
|
+
...(Object.keys(theme.defaultVariants).length ? { defaultVariants: theme.defaultVariants } : {})
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return `// astro.config.ts
|
|
33
|
+
import { ui } from "@rimelight/ui"
|
|
34
|
+
import { defineConfig } from "astro/config"
|
|
35
|
+
|
|
36
|
+
export default defineConfig({
|
|
37
|
+
integrations: [
|
|
38
|
+
ui({
|
|
39
|
+
components: {
|
|
40
|
+
${componentName}: ${JSON.stringify(componentConfig, null, 6).replace(/\n/g, "\n ")}
|
|
41
|
+
}
|
|
42
|
+
})
|
|
43
|
+
]
|
|
44
|
+
})`
|
|
45
|
+
}
|