@rimelight/ui 0.0.33 → 0.0.34

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.
Files changed (282) hide show
  1. package/README.md +157 -157
  2. package/package.json +105 -105
  3. package/src/components/astro/RLAAspectRatio.astro +2 -2
  4. package/src/components/astro/RLAButton.astro +141 -139
  5. package/src/components/astro/RLAChart.astro +19 -34
  6. package/src/components/astro/RLAHead.astro +427 -196
  7. package/src/components/astro/RLAHeader.astro +66 -59
  8. package/src/components/astro/RLAImage.astro +61 -19
  9. package/src/components/astro/RLALink.astro +22 -23
  10. package/src/components/astro/RLALinkGroup.astro +66 -4
  11. package/src/components/astro/RLALocaleSelector.astro +136 -113
  12. package/src/components/astro/RLAMain.astro +8 -3
  13. package/src/components/astro/RLAMarquee.astro +9 -2
  14. package/src/components/astro/RLANavigationMenu.astro +245 -56
  15. package/src/components/astro/RLAPagination.astro +2 -4
  16. package/src/components/astro/RLAProgress.astro +8 -2
  17. package/src/components/astro/RLASelect.astro +38 -24
  18. package/src/components/astro/RLASlideover.astro +66 -1
  19. package/src/components/astro/RLASlider.astro +14 -14
  20. package/src/components/astro/RLASplitter.astro +14 -2
  21. package/src/components/astro/RLAStickySurface.astro +9 -2
  22. package/src/components/vue/RLVCheckbox.vue +68 -68
  23. package/src/components/vue/RLVForm.vue +44 -44
  24. package/src/components/vue/RLVFormField.vue +58 -58
  25. package/src/components/vue/RLVInput.vue +93 -93
  26. package/src/components/vue/RLVPost.vue +63 -63
  27. package/src/components/vue/RLVPosts.vue +21 -21
  28. package/src/components/vue/RLVToast.vue +68 -68
  29. package/src/components/vue/RLVToaster.vue +29 -29
  30. package/src/composables/index.ts +3 -3
  31. package/src/composables/useHeaderStore.ts +13 -14
  32. package/src/composables/useScrollToTop.ts +62 -62
  33. package/src/composables/useToast.ts +43 -42
  34. package/src/config/index.ts +1 -0
  35. package/src/config/security.ts +220 -0
  36. package/src/integrations/ui.ts +199 -199
  37. package/src/middleware/security.ts +75 -15
  38. package/src/plugins/index.ts +2 -2
  39. package/src/plugins/starlightDocsApi/index.ts +272 -272
  40. package/src/presets/defaults.ts +297 -297
  41. package/src/presets/index.ts +683 -637
  42. package/src/styles/index.css +160 -160
  43. package/src/themes/3d-hover.theme.ts +13 -13
  44. package/src/themes/accordion.theme.ts +13 -13
  45. package/src/themes/alert.theme.ts +131 -125
  46. package/src/themes/app.theme.ts +13 -13
  47. package/src/themes/audio.theme.ts +13 -13
  48. package/src/themes/avatar.theme.ts +84 -84
  49. package/src/themes/badge.theme.ts +169 -163
  50. package/src/themes/banner.theme.ts +13 -13
  51. package/src/themes/browser-mockup.theme.ts +13 -13
  52. package/src/themes/button.theme.ts +224 -202
  53. package/src/themes/calendar.theme.ts +13 -13
  54. package/src/themes/card.theme.ts +42 -42
  55. package/src/themes/changelog.theme.ts +13 -13
  56. package/src/themes/chart.theme.ts +22 -22
  57. package/src/themes/chat-message.theme.ts +13 -13
  58. package/src/themes/chat-messages.theme.ts +13 -13
  59. package/src/themes/chat-palette.theme.ts +13 -13
  60. package/src/themes/chat-prompt-submit.theme.ts +13 -13
  61. package/src/themes/chat-prompt.theme.ts +13 -13
  62. package/src/themes/chat-reasoning.theme.ts +13 -13
  63. package/src/themes/chat-shimmer.theme.ts +13 -13
  64. package/src/themes/chat-tool.theme.ts +13 -13
  65. package/src/themes/chat.theme.ts +13 -13
  66. package/src/themes/checkbox.theme.ts +50 -50
  67. package/src/themes/chip.theme.ts +74 -74
  68. package/src/themes/color-picker.theme.ts +13 -13
  69. package/src/themes/command-palette.theme.ts +13 -13
  70. package/src/themes/compare.theme.ts +13 -13
  71. package/src/themes/confirm.theme.ts +48 -48
  72. package/src/themes/container.theme.ts +13 -13
  73. package/src/themes/context-menu.theme.ts +13 -13
  74. package/src/themes/dashboard-group.theme.ts +13 -13
  75. package/src/themes/dashboard-navbar.theme.ts +13 -13
  76. package/src/themes/dashboard-panel.theme.ts +13 -13
  77. package/src/themes/dashboard-resize-handle.theme.ts +13 -13
  78. package/src/themes/dashboard-search-button.theme.ts +13 -13
  79. package/src/themes/dashboard-search.theme.ts +13 -13
  80. package/src/themes/dashboard-sidebar-collapse.theme.ts +13 -13
  81. package/src/themes/dashboard-sidebar-toggle.theme.ts +13 -13
  82. package/src/themes/dashboard-sidebar.theme.ts +13 -13
  83. package/src/themes/dashboard-toolbar.theme.ts +13 -13
  84. package/src/themes/dock.theme.ts +13 -13
  85. package/src/themes/drawer.theme.ts +61 -61
  86. package/src/themes/dropdown-menu.theme.ts +29 -29
  87. package/src/themes/editor.theme.ts +13 -13
  88. package/src/themes/empty.theme.ts +13 -13
  89. package/src/themes/error.theme.ts +13 -13
  90. package/src/themes/field-group.theme.ts +24 -24
  91. package/src/themes/fieldset.theme.ts +13 -13
  92. package/src/themes/floating-action.theme.ts +13 -13
  93. package/src/themes/footer.theme.ts +30 -30
  94. package/src/themes/form.theme.ts +13 -13
  95. package/src/themes/gallery.theme.ts +13 -13
  96. package/src/themes/head.theme.ts +13 -13
  97. package/src/themes/header.theme.ts +45 -46
  98. package/src/themes/icon.theme.ts +23 -23
  99. package/src/themes/image.theme.ts +23 -19
  100. package/src/themes/input-date.theme.ts +13 -13
  101. package/src/themes/input-menu.theme.ts +35 -35
  102. package/src/themes/input-number.theme.ts +13 -13
  103. package/src/themes/input-tags.theme.ts +13 -13
  104. package/src/themes/input-time.theme.ts +13 -13
  105. package/src/themes/input.theme.ts +42 -42
  106. package/src/themes/kbd.theme.ts +95 -89
  107. package/src/themes/knob.theme.ts +13 -13
  108. package/src/themes/link-group.theme.ts +32 -32
  109. package/src/themes/link.theme.ts +13 -13
  110. package/src/themes/listbox.theme.ts +13 -13
  111. package/src/themes/locale-selector.theme.ts +49 -49
  112. package/src/themes/logo.theme.ts +13 -13
  113. package/src/themes/main.theme.ts +13 -13
  114. package/src/themes/megamenu.theme.ts +13 -13
  115. package/src/themes/meter.theme.ts +13 -13
  116. package/src/themes/navigation-menu.theme.ts +44 -27
  117. package/src/themes/page-aside.theme.ts +13 -13
  118. package/src/themes/page-body.theme.ts +13 -13
  119. package/src/themes/page-header.theme.ts +13 -13
  120. package/src/themes/page-section.theme.ts +187 -187
  121. package/src/themes/page.theme.ts +13 -13
  122. package/src/themes/password.theme.ts +13 -13
  123. package/src/themes/phone-mockup.theme.ts +13 -13
  124. package/src/themes/placeholder.theme.ts +15 -15
  125. package/src/themes/post.theme.ts +33 -33
  126. package/src/themes/pricing-plan.theme.ts +13 -13
  127. package/src/themes/pricing-table.theme.ts +13 -13
  128. package/src/themes/progress.theme.ts +38 -38
  129. package/src/themes/prose.theme.ts +13 -13
  130. package/src/themes/qr-code.theme.ts +13 -13
  131. package/src/themes/quote.theme.ts +13 -13
  132. package/src/themes/radio-group.theme.ts +13 -13
  133. package/src/themes/rating.theme.ts +13 -13
  134. package/src/themes/scroll-area.theme.ts +26 -26
  135. package/src/themes/scroll-to-top.theme.ts +52 -52
  136. package/src/themes/select.theme.ts +29 -29
  137. package/src/themes/separator.theme.ts +147 -147
  138. package/src/themes/sidebar.theme.ts +38 -38
  139. package/src/themes/skeleton.theme.ts +21 -21
  140. package/src/themes/slideover.theme.ts +4 -4
  141. package/src/themes/slider.theme.ts +36 -36
  142. package/src/themes/speed-dial.theme.ts +13 -13
  143. package/src/themes/spinner.theme.ts +34 -34
  144. package/src/themes/splitter.theme.ts +23 -23
  145. package/src/themes/spoiler.theme.ts +13 -13
  146. package/src/themes/stepper.theme.ts +13 -13
  147. package/src/themes/switch.theme.ts +67 -67
  148. package/src/themes/table-of-contents.theme.ts +48 -48
  149. package/src/themes/tabs.theme.ts +52 -52
  150. package/src/themes/textarea.theme.ts +27 -27
  151. package/src/themes/theme-selector.theme.ts +15 -15
  152. package/src/themes/timeline.theme.ts +13 -13
  153. package/src/themes/toast.theme.ts +31 -31
  154. package/src/themes/tooltip.theme.ts +28 -28
  155. package/src/themes/tour.theme.ts +13 -13
  156. package/src/themes/tree.theme.ts +13 -13
  157. package/src/themes/user.theme.ts +13 -13
  158. package/src/themes/video.theme.ts +17 -17
  159. package/src/themes/watermark.theme.ts +13 -13
  160. package/src/themes/window-mockup.theme.ts +13 -13
  161. package/src/types/components/3d-hover.ts +11 -11
  162. package/src/types/components/alert.ts +2 -2
  163. package/src/types/components/app.ts +11 -11
  164. package/src/types/components/audio.ts +11 -11
  165. package/src/types/components/avatar-group.ts +11 -11
  166. package/src/types/components/avatar.ts +60 -60
  167. package/src/types/components/badge.ts +2 -2
  168. package/src/types/components/banner.ts +11 -11
  169. package/src/types/components/browser-mockup.ts +11 -11
  170. package/src/types/components/button.ts +74 -81
  171. package/src/types/components/calendar.ts +11 -11
  172. package/src/types/components/changelog.ts +11 -11
  173. package/src/types/components/chart.ts +2 -1
  174. package/src/types/components/chat-message.ts +11 -11
  175. package/src/types/components/chat-messages.ts +11 -11
  176. package/src/types/components/chat-palette.ts +11 -11
  177. package/src/types/components/chat-prompt-submit.ts +11 -11
  178. package/src/types/components/chat-prompt.ts +11 -11
  179. package/src/types/components/chat-reasoning.ts +11 -11
  180. package/src/types/components/chat-shimmer.ts +11 -11
  181. package/src/types/components/chat-tool.ts +11 -11
  182. package/src/types/components/chat.ts +11 -11
  183. package/src/types/components/checkbox.ts +2 -2
  184. package/src/types/components/chip.ts +17 -17
  185. package/src/types/components/collapsible.ts +2 -1
  186. package/src/types/components/color-picker.ts +11 -11
  187. package/src/types/components/command-palette.ts +11 -11
  188. package/src/types/components/compare.ts +11 -11
  189. package/src/types/components/container.ts +17 -17
  190. package/src/types/components/context-menu.ts +11 -11
  191. package/src/types/components/dashboard-group.ts +11 -11
  192. package/src/types/components/dashboard-navbar.ts +11 -11
  193. package/src/types/components/dashboard-panel.ts +11 -11
  194. package/src/types/components/dashboard-resize-handle.ts +11 -11
  195. package/src/types/components/dashboard-search-button.ts +11 -11
  196. package/src/types/components/dashboard-search.ts +11 -11
  197. package/src/types/components/dashboard-sidebar-collapse.ts +11 -11
  198. package/src/types/components/dashboard-sidebar-toggle.ts +11 -11
  199. package/src/types/components/dashboard-sidebar.ts +11 -11
  200. package/src/types/components/dashboard-toolbar.ts +11 -11
  201. package/src/types/components/dock.ts +11 -11
  202. package/src/types/components/dropdown-menu.ts +2 -1
  203. package/src/types/components/editor.ts +11 -11
  204. package/src/types/components/empty.ts +11 -11
  205. package/src/types/components/error.ts +11 -11
  206. package/src/types/components/fieldset.ts +11 -11
  207. package/src/types/components/file-upload.ts +2 -1
  208. package/src/types/components/floating-action.ts +11 -11
  209. package/src/types/components/form.ts +19 -19
  210. package/src/types/components/gallery.ts +11 -11
  211. package/src/types/components/icon.ts +19 -19
  212. package/src/types/components/image.ts +32 -28
  213. package/src/types/components/index.ts +134 -135
  214. package/src/types/components/input-date.ts +11 -11
  215. package/src/types/components/input-menu.ts +44 -43
  216. package/src/types/components/input-number.ts +11 -11
  217. package/src/types/components/input-pin.ts +36 -35
  218. package/src/types/components/input-tags.ts +11 -11
  219. package/src/types/components/input-time.ts +11 -11
  220. package/src/types/components/input.ts +2 -2
  221. package/src/types/components/knob.ts +11 -11
  222. package/src/types/components/link-group.ts +45 -23
  223. package/src/types/components/link.ts +4 -0
  224. package/src/types/components/listbox.ts +11 -11
  225. package/src/types/components/locale-selector.ts +33 -33
  226. package/src/types/components/logo.ts +32 -31
  227. package/src/types/components/main.ts +17 -17
  228. package/src/types/components/megamenu.ts +11 -11
  229. package/src/types/components/meter.ts +11 -11
  230. package/src/types/components/modal.ts +39 -39
  231. package/src/types/components/navigation-menu.ts +59 -54
  232. package/src/types/components/page-aside.ts +11 -11
  233. package/src/types/components/page-body.ts +11 -11
  234. package/src/types/components/page-header.ts +11 -11
  235. package/src/types/components/page.ts +11 -11
  236. package/src/types/components/password.ts +11 -11
  237. package/src/types/components/phone-mockup.ts +5 -5
  238. package/src/types/components/pin-input.ts +11 -11
  239. package/src/types/components/popover.ts +23 -23
  240. package/src/types/components/post.ts +35 -35
  241. package/src/types/components/pricing-plan.ts +11 -11
  242. package/src/types/components/pricing-table.ts +11 -11
  243. package/src/types/components/progress.ts +33 -33
  244. package/src/types/components/prose.ts +11 -11
  245. package/src/types/components/qr-code.ts +11 -11
  246. package/src/types/components/radio-group.ts +11 -11
  247. package/src/types/components/rating.ts +11 -11
  248. package/src/types/components/scroll-area.ts +23 -23
  249. package/src/types/components/scroll-to-top.ts +2 -2
  250. package/src/types/components/select.ts +2 -1
  251. package/src/types/components/separator.ts +19 -19
  252. package/src/types/components/sidebar.ts +87 -72
  253. package/src/types/components/slideover.ts +31 -31
  254. package/src/types/components/slider.ts +2 -2
  255. package/src/types/components/speed-dial.ts +11 -11
  256. package/src/types/components/spinner.ts +25 -25
  257. package/src/types/components/splitter.ts +19 -19
  258. package/src/types/components/spoiler.ts +11 -11
  259. package/src/types/components/stepper.ts +11 -11
  260. package/src/types/components/sticky-surface.ts +19 -19
  261. package/src/types/components/switch.ts +41 -41
  262. package/src/types/components/table-of-contents.ts +17 -17
  263. package/src/types/components/tabs.ts +2 -1
  264. package/src/types/components/textarea.ts +2 -1
  265. package/src/types/components/theme-selector.ts +15 -15
  266. package/src/types/components/timeline.ts +11 -11
  267. package/src/types/components/toast.ts +2 -1
  268. package/src/types/components/tooltip.ts +23 -23
  269. package/src/types/components/tour.ts +11 -11
  270. package/src/types/components/tree.ts +11 -11
  271. package/src/types/components/user.ts +11 -11
  272. package/src/types/components/watermark.ts +11 -11
  273. package/src/types/components/window-mockup.ts +11 -11
  274. package/src/types/theme.ts +10 -0
  275. package/src/utils/docs.ts +215 -189
  276. package/src/utils/headerStack.ts +95 -95
  277. package/src/utils/index.ts +5 -5
  278. package/src/components/astro/RLABlogPost.astro +0 -7
  279. package/src/components/astro/RLABlogPosts.astro +0 -9
  280. package/src/components/astro/RLAPosts.astro +0 -23
  281. package/src/themes/posts.theme.ts +0 -13
  282. package/src/types/components/posts.ts +0 -11
@@ -38,28 +38,75 @@ export const security = defineMiddleware(async (context, next) => {
38
38
  return addSecurityHeaders(response)
39
39
  }
40
40
 
41
+ const cspHeader = response.headers.get("content-security-policy")
42
+ const nonceValue = cspHeader ? crypto.randomUUID().replace(/-/g, "") : null
43
+ const newHeaders = new Headers(response.headers)
44
+
45
+ if (cspHeader && nonceValue) {
46
+ let newCsp = cspHeader
47
+ if (newCsp.includes("script-src ")) {
48
+ newCsp = newCsp.replace("script-src ", `script-src 'nonce-${nonceValue}' `)
49
+ } else {
50
+ newCsp = newCsp + `; script-src 'nonce-${nonceValue}'`
51
+ }
52
+ if (newCsp.includes("style-src ")) {
53
+ newCsp = newCsp.replace("style-src ", `style-src 'nonce-${nonceValue}' `)
54
+ } else {
55
+ newCsp = newCsp + `; style-src 'nonce-${nonceValue}'`
56
+ }
57
+ newHeaders.set("content-security-policy", newCsp)
58
+ }
59
+
41
60
  if (typeof HTMLRewriter !== "undefined") {
42
- return addSecurityHeaders(
43
- new HTMLRewriter()
44
- .on("script[src]", {
61
+ let rewriter = new HTMLRewriter()
62
+ .on("script[src]", {
63
+ element(el: any) {
64
+ const src = el.getAttribute("src")
65
+ if (src && manifest[src]) {
66
+ el.setAttribute("integrity", manifest[src])
67
+ el.setAttribute("crossorigin", "anonymous")
68
+ }
69
+ }
70
+ })
71
+ .on('link[rel="stylesheet"][href]', {
72
+ element(el: any) {
73
+ const href = el.getAttribute("href")
74
+ if (href && manifest[href]) {
75
+ el.setAttribute("integrity", manifest[href])
76
+ el.setAttribute("crossorigin", "anonymous")
77
+ }
78
+ }
79
+ })
80
+
81
+ if (nonceValue) {
82
+ rewriter = rewriter
83
+ .on("head", {
45
84
  element(el: any) {
46
- const src = el.getAttribute("src")
47
- if (src && manifest[src]) {
48
- el.setAttribute("integrity", manifest[src])
49
- el.setAttribute("crossorigin", "anonymous")
50
- }
85
+ el.append(`<meta name="csp-nonce" content="${nonceValue}">`, { html: true })
51
86
  }
52
87
  })
53
- .on('link[rel="stylesheet"][href]', {
88
+ .on("script", {
54
89
  element(el: any) {
55
- const href = el.getAttribute("href")
56
- if (href && manifest[href]) {
57
- el.setAttribute("integrity", manifest[href])
58
- el.setAttribute("crossorigin", "anonymous")
90
+ if (!el.getAttribute("nonce")) {
91
+ el.setAttribute("nonce", nonceValue)
59
92
  }
60
93
  }
61
94
  })
62
- .transform(response)
95
+ .on("style", {
96
+ element(el: any) {
97
+ el.setAttribute("nonce", nonceValue)
98
+ }
99
+ })
100
+ }
101
+
102
+ return addSecurityHeaders(
103
+ rewriter.transform(
104
+ new Response(response.body, {
105
+ status: response.status,
106
+ statusText: response.statusText,
107
+ headers: newHeaders
108
+ })
109
+ )
63
110
  )
64
111
  }
65
112
 
@@ -84,11 +131,24 @@ export const security = defineMiddleware(async (context, next) => {
84
131
  )
85
132
  }
86
133
 
134
+ if (nonceValue) {
135
+ modifiedHtml = modifiedHtml.replace(/<head([^>]*)>/i, `<head$1>\n<meta name="csp-nonce" content="${nonceValue}">`)
136
+ modifiedHtml = modifiedHtml.replace(
137
+ /<script(?![^>]*nonce=)([^>]*)>/gi,
138
+ `<script$1 nonce="${nonceValue}">`
139
+ )
140
+ modifiedHtml = modifiedHtml.replace(
141
+ /<style(?![^>]*nonce=)([^>]*)>/gi,
142
+ `<style$1 nonce="${nonceValue}">`
143
+ )
144
+ }
145
+
87
146
  return addSecurityHeaders(
88
147
  new Response(modifiedHtml, {
89
148
  status: response.status,
90
149
  statusText: response.statusText,
91
- headers: new Headers(response.headers)
150
+ headers: newHeaders
92
151
  })
93
152
  )
94
153
  })
154
+
@@ -1,2 +1,2 @@
1
- export { default as starlightDocsApi } from "./starlightDocsApi"
2
- export type { StarlightDocsApiConfig } from "./starlightDocsApi"
1
+ export { default as starlightDocsApi } from "./starlightDocsApi"
2
+ export type { StarlightDocsApiConfig } from "./starlightDocsApi"
@@ -1,272 +1,272 @@
1
- import type { StarlightPlugin } from "@astrojs/starlight/types"
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 pathDirname = path.dirname(fileURLToPath(import.meta.url))
11
- const uiRoot = path.resolve(pathDirname, "..", "..", "..")
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): StarlightPlugin {
245
- return {
246
- name: "rimelight-ui-starlight-docs-api",
247
- hooks: {
248
- "config:setup"({ addIntegration, logger }) {
249
- logger.info("Initializing @rimelight/ui docs API")
250
-
251
- const meta = extractAllMeta()
252
-
253
- addIntegration({
254
- name: "rimelight-ui-starlight-docs-api-integration",
255
- hooks: {
256
- "astro:config:setup": ({ updateConfig }) => {
257
- updateConfig({
258
- vite: {
259
- plugins: [
260
- virtual({
261
- "virtual:rimelight-ui-docs-meta": `export default ${JSON.stringify(meta)}`
262
- })
263
- ]
264
- }
265
- } as Record<string, unknown>)
266
- }
267
- }
268
- })
269
- }
270
- }
271
- }
272
- }
1
+ import type { StarlightPlugin } from "@astrojs/starlight/types"
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 pathDirname = path.dirname(fileURLToPath(import.meta.url))
11
+ const uiRoot = path.resolve(pathDirname, "..", "..", "..")
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): StarlightPlugin {
245
+ return {
246
+ name: "rimelight-ui-starlight-docs-api",
247
+ hooks: {
248
+ "config:setup"({ addIntegration, logger }) {
249
+ logger.info("Initializing @rimelight/ui docs API")
250
+
251
+ const meta = extractAllMeta()
252
+
253
+ addIntegration({
254
+ name: "rimelight-ui-starlight-docs-api-integration",
255
+ hooks: {
256
+ "astro:config:setup": ({ updateConfig }) => {
257
+ updateConfig({
258
+ vite: {
259
+ plugins: [
260
+ virtual({
261
+ "virtual:rimelight-ui-docs-meta": `export default ${JSON.stringify(meta)}`
262
+ })
263
+ ]
264
+ }
265
+ } as Record<string, unknown>)
266
+ }
267
+ }
268
+ })
269
+ }
270
+ }
271
+ }
272
+ }