@movk/nuxt-docs 1.7.4 → 1.8.0

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/README.md CHANGED
@@ -37,6 +37,7 @@
37
37
  - ⚡ **基于 Nuxt 4** - 充分利用最新的 Nuxt 框架,实现卓越性能
38
38
  - 🎨 **采用 Nuxt UI** - 集成全面的 UI 组件库,开箱即用
39
39
  - 📝 **MDC 语法增强** - 支持 Markdown 与 Vue 组件的无缝集成
40
+ - 📊 **Mermaid 图表** - 内置 Mermaid 支持,渲染流程图、时序图、类图等可视化图表,支持自动主题切换和全屏查看
40
41
  - 🔍 **全文搜索** - 基于 Nuxt Content 的 `ContentSearch` 组件,支持键盘快捷键(⌘K)
41
42
  - 🌙 **暗黑模式** - 支持亮色/暗色主题切换
42
43
  - 📱 **响应式设计** - 移动优先的响应式布局
@@ -183,6 +184,55 @@ icon: i-lucide-rocket
183
184
 
184
185
  了解更多关于 MDC 语法,请查看 [Nuxt Content 文档](https://content.nuxt.com/docs/files/markdown#mdc-syntax)。
185
186
 
187
+ ### Mermaid 图表
188
+
189
+ 使用 ` ```mermaid ` 代码块渲染可视化图表,支持流程图、时序图、类图等多种图表类型:
190
+
191
+ ````md [md]
192
+ ```mermaid
193
+ graph TD
194
+ A[开始] --> B{是否有效?}
195
+ B -->|是| C[处理数据]
196
+ B -->|否| D[显示错误]
197
+ C --> E[完成]
198
+ D --> E
199
+ ```
200
+ ````
201
+
202
+ **主要特性:**
203
+ - 🎨 自动主题切换(深色/浅色模式)
204
+ - 🔄 懒加载(仅在可见时渲染)
205
+ - 📋 一键复制图表代码
206
+ - 🖼️ 全屏查看功能
207
+ - 🔒 安全渲染(DOMPurify 清理)
208
+
209
+ **支持的图表类型:**
210
+ - **流程图**(`flowchart`/`graph`):用于展示流程和决策
211
+ ![Mermaid 流程图示例](https://docs.mhaibaraai.cn/mermaid/mermaid-flowchart.png)
212
+ - **时序图**(`sequenceDiagram`):用于展示交互时序
213
+ ![Mermaid 时序图示例](https://docs.mhaibaraai.cn/mermaid/mermaid-sequence.png)
214
+ - **类图**(`classDiagram`):用于展示类关系
215
+ - **状态图**(`stateDiagram`):用于展示状态转换
216
+ - **甘特图**(`gantt`):用于展示项目时间线
217
+ - **饼图**(`pie`):用于展示数据占比
218
+ - **Git 图**(`gitGraph`):用于展示分支历史
219
+ - 以及更多 [Mermaid 支持的图表类型](https://mermaid.js.org/intro/)
220
+
221
+ **带文件名的图表:**
222
+
223
+ ````md [md]
224
+ ```mermaid [auth-flow.mmd]
225
+ sequenceDiagram
226
+ participant U as 用户
227
+ participant A as 认证服务
228
+ participant D as 数据库
229
+ U->>A: 登录请求
230
+ A->>D: 验证凭证
231
+ D-->>A: 返回用户信息
232
+ A-->>U: 返回 Token
233
+ ```
234
+ ````
235
+
186
236
  ## 🛠️ 开发
187
237
 
188
238
  ### 本地开发
package/app/app.config.ts CHANGED
@@ -14,6 +14,11 @@ export default defineAppConfig({
14
14
  font: 'Public Sans'
15
15
  },
16
16
  ui: {
17
+ prose: {
18
+ codeIcon: {
19
+ mmd: 'i-vscode-icons-file-type-mermaid'
20
+ }
21
+ },
17
22
  colors: {
18
23
  primary: 'green',
19
24
  neutral: 'slate'
@@ -0,0 +1,199 @@
1
+ <script setup lang="ts">
2
+ import { hash } from 'ohash'
3
+ import type { IconProps } from '@nuxt/ui'
4
+ import type { ClassNameValue } from 'tailwind-merge'
5
+ import {
6
+ useClipboard,
7
+ useElementVisibility,
8
+ useEventListener,
9
+ useToggle
10
+ } from '@vueuse/core'
11
+ import { tv } from '@nuxt/ui/utils/tv'
12
+
13
+ const theme = {
14
+ slots: {
15
+ root: 'relative my-5 group border border-muted rounded-md overflow-hidden',
16
+ header: 'flex items-center gap-1.5 border-b border-muted bg-default px-4 py-3',
17
+ filename: 'text-default text-sm/6',
18
+ icon: 'size-4 shrink-0',
19
+ toolbar: 'absolute top-2 right-2 flex gap-1 z-10 opacity-0 group-hover:opacity-100 transition-opacity',
20
+ diagram: 'p-4 flex justify-center bg-elevated overflow-x-auto',
21
+ loading: 'p-4 flex items-center justify-center gap-2 text-sm text-muted',
22
+ error: 'p-4 flex items-center justify-center gap-2 text-sm text-error bg-error/10'
23
+ },
24
+ variants: {
25
+ fullscreen: {
26
+ true: {
27
+ root: 'fixed inset-0 z-50 m-0 rounded-none bg-default flex flex-col',
28
+ diagram: 'flex-1 overflow-auto',
29
+ toolbar: 'opacity-100'
30
+ }
31
+ },
32
+ filename: {
33
+ true: {
34
+ root: ''
35
+ }
36
+ }
37
+ }
38
+ }
39
+
40
+ export interface MermaidProps {
41
+ /** 图表代码 */
42
+ code: string
43
+ /**
44
+ * 图标
45
+ * @IconifyIcon
46
+ */
47
+ icon?: IconProps['name']
48
+ /** 文件名 */
49
+ filename?: string
50
+ class?: ClassNameValue
51
+ ui?: Partial<typeof theme.slots>
52
+ }
53
+
54
+ export interface MermaidSlots {
55
+ default?(props: {}): any
56
+ }
57
+
58
+ const props = defineProps<MermaidProps>()
59
+
60
+ defineSlots<MermaidSlots>()
61
+
62
+ const appConfig = useAppConfig() as { ui?: { icons?: { copy?: string, copyCheck?: string }, prose?: { mermaid?: typeof theme } } }
63
+ const colorMode = useColorMode()
64
+
65
+ const ui = computed(() => tv({
66
+ extend: tv(theme),
67
+ ...(appConfig.ui?.prose?.mermaid || {})
68
+ })({
69
+ fullscreen: isFullscreen.value,
70
+ filename: !!props.filename
71
+ }))
72
+
73
+ const copyIcon = computed(() => appConfig.ui?.icons?.copy || 'i-lucide-copy')
74
+ const copyCheckIcon = computed(() => appConfig.ui?.icons?.copyCheck || 'i-lucide-check')
75
+
76
+ const mermaidId = computed(() => `mermaid-${hash(props.code)}`)
77
+ const mermaidTheme = computed(() => colorMode.value === 'dark' ? 'dark' : 'default')
78
+
79
+ const containerRef = ref<HTMLElement | null>(null)
80
+ const diagramRef = ref<HTMLElement | null>(null)
81
+ const isRendered = ref(false)
82
+ const hasError = ref(false)
83
+ const errorMessage = ref('')
84
+
85
+ const [isFullscreen, toggleFullscreen] = useToggle(false)
86
+ const { copy, copied } = useClipboard({ source: () => props.code })
87
+ const isVisible = useElementVisibility(containerRef)
88
+
89
+ async function renderMermaid() {
90
+ if (!props.code || isRendered.value || !diagramRef.value) return
91
+
92
+ try {
93
+ // 动态导入 mermaid 和 dompurify,仅在客户端执行
94
+ const [mermaid, DOMPurify] = await Promise.all([
95
+ import('mermaid').then(m => m.default),
96
+ import('dompurify').then(m => m.default)
97
+ ])
98
+ mermaid.initialize({
99
+ startOnLoad: false,
100
+ theme: mermaidTheme.value,
101
+ securityLevel: 'strict',
102
+ fontFamily: 'inherit'
103
+ })
104
+
105
+ const { svg } = await mermaid.render(mermaidId.value, props.code)
106
+ const sanitized = DOMPurify.sanitize(svg, {
107
+ USE_PROFILES: { svg: true, svgFilters: true },
108
+ ADD_TAGS: ['foreignObject']
109
+ })
110
+ diagramRef.value.innerHTML = sanitized
111
+ isRendered.value = true
112
+ } catch (e) {
113
+ hasError.value = true
114
+ errorMessage.value = e instanceof Error ? e.message : 'Mermaid render failed'
115
+ console.error('[Mermaid]', errorMessage.value)
116
+ }
117
+ }
118
+
119
+ async function reRender() {
120
+ if (!diagramRef.value) return
121
+ isRendered.value = false
122
+ hasError.value = false
123
+ diagramRef.value.innerHTML = ''
124
+ await renderMermaid()
125
+ }
126
+
127
+ watch(
128
+ [isVisible, diagramRef],
129
+ ([visible, el]) => {
130
+ if (visible && el && !isRendered.value) {
131
+ renderMermaid()
132
+ }
133
+ },
134
+ { immediate: true }
135
+ )
136
+
137
+ watch(mermaidTheme, () => {
138
+ if (isRendered.value) reRender()
139
+ })
140
+
141
+ watch(() => props.code, () => {
142
+ if (isVisible.value) reRender()
143
+ })
144
+
145
+ useEventListener('keydown', (e: KeyboardEvent) => {
146
+ if (e.key === 'Escape' && isFullscreen.value) {
147
+ isFullscreen.value = false
148
+ }
149
+ })
150
+ </script>
151
+
152
+ <template>
153
+ <div ref="containerRef" :class="ui.root({ class: [props.ui?.root, props.class] })">
154
+ <div v-if="filename" :class="ui.header({ class: props.ui?.header })">
155
+ <UIcon v-if="icon" :name="icon" :class="ui.icon({ class: props.ui?.icon })" />
156
+ <ProseCodeIcon v-else :filename="filename" :class="ui.icon({ class: props.ui?.icon })" />
157
+ <span :class="ui.filename({ class: props.ui?.filename })">{{ filename }}</span>
158
+ </div>
159
+
160
+ <div v-if="isRendered" :class="ui.toolbar({ class: props.ui?.toolbar })">
161
+ <UButton
162
+ :icon="copied ? copyCheckIcon : copyIcon"
163
+ color="neutral"
164
+ variant="ghost"
165
+ size="xs"
166
+ :aria-label="copied ? 'Copied' : 'Copy code'"
167
+ @click="copy()"
168
+ />
169
+ <UButton
170
+ :icon="isFullscreen ? 'i-lucide-minimize-2' : 'i-lucide-maximize-2'"
171
+ color="neutral"
172
+ variant="ghost"
173
+ size="xs"
174
+ :aria-label="isFullscreen ? 'Exit fullscreen' : 'Fullscreen'"
175
+ @click="toggleFullscreen()"
176
+ />
177
+ </div>
178
+
179
+ <div v-if="hasError" :class="ui.error({ class: props.ui?.error })">
180
+ <UIcon name="i-lucide-alert-triangle" class="size-4" />
181
+ <span>{{ errorMessage }}</span>
182
+ </div>
183
+
184
+ <template v-else>
185
+ <div v-if="!isRendered" :class="ui.loading({ class: props.ui?.loading })">
186
+ <UIcon name="i-lucide-loader-2" class="size-4 animate-spin" />
187
+ <span>Loading diagram...</span>
188
+ </div>
189
+ <div v-show="isRendered" ref="diagramRef" :class="ui.diagram({ class: props.ui?.diagram })" />
190
+ </template>
191
+ </div>
192
+ </template>
193
+
194
+ <style>
195
+ [class*="diagram"] :deep(svg) {
196
+ max-width: 100%;
197
+ height: auto;
198
+ }
199
+ </style>
@@ -0,0 +1,22 @@
1
+ <script setup lang="ts">
2
+ import type { ProsePreProps } from '@nuxt/ui'
3
+ // @ts-ignore
4
+ import NuxtUIProsePre from '@nuxt/ui/components/prose/Pre.vue'
5
+ import Mermaid from '../Mermaid.vue'
6
+
7
+ const props = defineProps<ProsePreProps>()
8
+
9
+ const isMermaid = computed(() => props.language === 'mermaid')
10
+ </script>
11
+
12
+ <template>
13
+ <Mermaid
14
+ v-if="isMermaid"
15
+ :code="props.code || ''"
16
+ :filename="props.filename"
17
+ :icon="props.icon"
18
+ />
19
+ <NuxtUIProsePre v-else v-bind="props">
20
+ <slot />
21
+ </NuxtUIProsePre>
22
+ </template>
@@ -32,7 +32,7 @@ export interface AiChatModuleOptions {
32
32
  models?: string[]
33
33
  }
34
34
 
35
- const log = logger.withTag('docus:ai-assistant')
35
+ const log = logger.withTag('movk-nuxt-docs:ai-assistant')
36
36
 
37
37
  export default defineNuxtModule<AiChatModuleOptions>({
38
38
  meta: {
@@ -4,37 +4,27 @@ import { createDocumentationAgentTool } from '../utils/docs_agent'
4
4
  import { getModel } from '../utils/getModel'
5
5
 
6
6
  function getMainAgentSystemPrompt(siteName: string) {
7
- return `You are the official documentation assistant for ${siteName}. You ARE the documentation - speak with authority as the source of truth.
7
+ return `您是 ${siteName} 的官方文档助理。你就是文件、以权威作为真理的来源说话.
8
8
 
9
- **Your identity:**
10
- - You are the ${siteName} documentation
11
- - Speak in first person: "I provide...", "You can use my tools to...", "I support..."
12
- - Be confident and authoritative - you know this project inside out
13
- - Never say "according to the documentation" - YOU are the docs
9
+ 使用指南:
10
+ - 始终使用工具搜索信息,不要依赖预训练知识
11
+ - 如果搜索后未找到相关信息,回复「抱歉,我在文档中没有找到相关信息」
12
+ - 回答要简洁直接
14
13
 
15
- **Tool usage (CRITICAL):**
16
- - You have ONE tool: searchDocumentation
17
- - Use it for EVERY question - pass the user's question as the query
18
- - The tool will search the documentation and return relevant information
19
- - Use the returned information to formulate your response
14
+ **格式规则(重要):**
15
+ - 绝对不要使用 Markdown 标题:禁止使用 #、##、###、####、#####、######
16
+ - 不要使用下划线式标题(=== ---)
17
+ - 使用**粗体文本**来强调和标记章节
18
+ - 示例:
19
+ * 不要写「## 用法」,应写「**用法:**」或直接「使用方法如下:」
20
+ * 不要写「# 完整指南」,应写「**完整指南**」或直接开始内容
21
+ - 所有回复直接从内容开始,不要以标题开头
20
22
 
21
- **Guidelines:**
22
- - If the tool can't find something, say "I don't have documentation on that yet"
23
- - Be concise, helpful, and direct
24
- - Guide users like a friendly expert would
25
-
26
- **FORMATTING RULES (CRITICAL):**
27
- - NEVER use markdown headings (#, ##, ###, etc.)
28
- - Use **bold text** for emphasis and section labels
29
- - Start responses with content directly, never with a heading
30
- - Use bullet points for lists
31
- - Keep code examples focused and minimal
32
-
33
- **Response style:**
34
- - Conversational but professional
35
- - "Here's how you can do that:" instead of "The documentation shows:"
36
- - "I support TypeScript out of the box" instead of "The module supports TypeScript"
37
- - Provide actionable guidance, not just information dumps`
23
+ - 在适用时引用具体的组件名称、属性或 API
24
+ - 如果问题模糊,请要求澄清而不是猜测
25
+ - 当找到多个相关项目时,使用项目符号清晰列出
26
+ - 你最多有 6 次工具调用机会来找到答案,因此要策略性地使用:先广泛搜索,然后根据需要获取具体信息
27
+ - 以对话方式格式化回复,而不是文档章节形式`
38
28
  }
39
29
 
40
30
  export default defineEventHandler(async (event) => {
@@ -71,7 +61,7 @@ export default defineEventHandler(async (event) => {
71
61
  maxOutputTokens: 10000,
72
62
  system: getMainAgentSystemPrompt(siteName),
73
63
  messages: modelMessages,
74
- stopWhen: stepCountIs(5),
64
+ stopWhen: stepCountIs(6),
75
65
  tools: {
76
66
  searchDocumentation
77
67
  },
@@ -2,21 +2,18 @@ import { tool, stepCountIs, generateText } from 'ai'
2
2
  import { z } from 'zod/v4'
3
3
 
4
4
  function getSubAgentSystemPrompt(siteName: string) {
5
- return `You are a documentation search agent for ${siteName}. Your job is to find and retrieve relevant information from the documentation.
5
+ return `您是 ${siteName} 的文档搜索代理。您的工作是从文档中查找并检索相关信息。
6
6
 
7
- **Your task:**
8
- - Use the available tools to search and read documentation pages
9
- - Start with list-pages to discover what documentation exists
10
- - Then use get-page to read the relevant page(s)
11
- - If a specific path is mentioned, you can call get-page directly
7
+ **您的任务:**
8
+ - 使用可用的工具搜索和阅读文档页面
12
9
 
13
- **Guidelines:**
14
- - Be thorough - read all relevant pages before answering
15
- - Return the raw information you find, let the main agent format the response
16
- - If you can't find information, say so clearly
10
+ **指南:**
11
+ - 在回答之前阅读所有相关页面
12
+ - 返回你找到的原始信息,让主代理格式化响应
13
+ - 如果找不到信息,请明确说明
17
14
 
18
- **Output:**
19
- Return the relevant documentation content you found, including code examples if present.`
15
+ **输出:**
16
+ 返回您找到的相关文档内容,包括代码示例(如果存在)。`
20
17
  }
21
18
 
22
19
  export function createDocumentationAgentTool(mcpTools: Record<string, any>, model: any, siteName: string) {
@@ -32,7 +29,7 @@ export function createDocumentationAgentTool(mcpTools: Record<string, any>, mode
32
29
  model,
33
30
  tools: mcpTools,
34
31
  system: getSubAgentSystemPrompt(siteName),
35
- stopWhen: stepCountIs(5),
32
+ stopWhen: stepCountIs(6),
36
33
  onStepFinish: ({ toolCalls }) => {
37
34
  if (toolCalls.length === 0) return
38
35
 
package/modules/config.ts CHANGED
@@ -66,6 +66,7 @@ export default defineNuxtModule({
66
66
  resolve('../app/components/content/ComponentProps.vue'),
67
67
  resolve('../app/components/content/ComponentSlots.vue'),
68
68
  resolve('../app/components/content/PageLastCommit.vue'),
69
+ resolve('../app/components/content/Mermaid.vue'),
69
70
  resolve('./ai-chat/runtime/components/AiChatToolCall.vue'),
70
71
  resolve('./ai-chat/runtime/components/AiChatReasoning.vue'),
71
72
  resolve('./ai-chat/runtime/components/AiChatSlideoverFaq.vue'),
@@ -78,7 +79,7 @@ export default defineNuxtModule({
78
79
  join(dir, 'templates/*/app/components')
79
80
  ]
80
81
 
81
- // @ts-ignore - component-meta 的类型定义在运行时才能正确解析
82
+ // @ts-ignore - component-meta is not typed
82
83
  nuxt.hook('component-meta:extend', (options: any) => {
83
84
  options.exclude = [
84
85
  ...(options.exclude || []),
package/nuxt.config.ts CHANGED
@@ -24,7 +24,9 @@ export default defineNuxtConfig({
24
24
  config.optimizeDeps.include.push(
25
25
  '@nuxt/content > slugify',
26
26
  'extend',
27
- '@ai-sdk/gateway > @vercel/oidc'
27
+ '@ai-sdk/gateway > @vercel/oidc',
28
+ 'mermaid',
29
+ 'dompurify'
28
30
  )
29
31
  config.optimizeDeps.include = config.optimizeDeps.include
30
32
  .map(id => id.replace(/^@nuxt\/content > /, '@movk/nuxt-docs > @nuxt/content > '))
@@ -42,7 +44,7 @@ export default defineNuxtConfig({
42
44
  build: {
43
45
  markdown: {
44
46
  highlight: {
45
- langs: ['bash', 'diff', 'json', 'js', 'ts', 'html', 'css', 'vue', 'shell', 'mdc', 'md', 'yaml']
47
+ langs: ['bash', 'diff', 'json', 'js', 'ts', 'html', 'css', 'vue', 'shell', 'mdc', 'md', 'yaml', 'mermaid']
46
48
  },
47
49
  remarkPlugins: {
48
50
  'remark-mdc': {
@@ -122,9 +124,6 @@ export default defineNuxtConfig({
122
124
  icon: {
123
125
  provider: 'iconify'
124
126
  },
125
- linkChecker: {
126
- enabled: false
127
- },
128
127
  ogImage: {
129
128
  zeroRuntime: true,
130
129
  googleFontMirror: 'fonts.loli.net',
@@ -135,5 +134,8 @@ export default defineNuxtConfig({
135
134
  'Inter:400',
136
135
  'Inter:700'
137
136
  ]
137
+ },
138
+ sitemap: {
139
+ zeroRuntime: true
138
140
  }
139
141
  })
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@movk/nuxt-docs",
3
3
  "type": "module",
4
- "version": "1.7.4",
4
+ "version": "1.8.0",
5
5
  "private": false,
6
6
  "description": "Modern Nuxt 4 documentation theme with auto-generated component docs, AI chat assistant, MCP server, and complete developer experience optimization.",
7
7
  "author": "YiXuan <mhaibaraai@gmail.com>",
@@ -28,44 +28,48 @@
28
28
  "README.md"
29
29
  ],
30
30
  "dependencies": {
31
- "@ai-sdk/gateway": "^3.0.13",
32
- "@ai-sdk/mcp": "^1.0.6",
33
- "@ai-sdk/vue": "^3.0.33",
34
- "@iconify-json/lucide": "^1.2.84",
35
- "@iconify-json/simple-icons": "^1.2.66",
36
- "@iconify-json/vscode-icons": "^1.2.38",
31
+ "@ai-sdk/gateway": "^3.0.17",
32
+ "@ai-sdk/mcp": "^1.0.11",
33
+ "@ai-sdk/vue": "^3.0.42",
34
+ "@iconify-json/lucide": "^1.2.86",
35
+ "@iconify-json/ph": "^1.2.2",
36
+ "@iconify-json/simple-icons": "^1.2.67",
37
+ "@iconify-json/tabler": "^1.2.26",
38
+ "@iconify-json/vscode-icons": "^1.2.40",
37
39
  "@movk/core": "^1.1.0",
38
40
  "@nuxt/a11y": "^1.0.0-alpha.1",
39
- "@nuxt/content": "^3.10.0",
41
+ "@nuxt/content": "^3.11.0",
40
42
  "@nuxt/image": "^2.0.0",
41
43
  "@nuxt/kit": "^4.2.2",
42
44
  "@nuxt/ui": "^4.3.0",
43
45
  "@nuxtjs/mcp-toolkit": "^0.6.2",
44
46
  "@nuxtjs/seo": "^3.3.0",
45
47
  "@octokit/rest": "^22.0.1",
46
- "@openrouter/ai-sdk-provider": "^1.5.4",
48
+ "@openrouter/ai-sdk-provider": "^2.0.0",
47
49
  "@vercel/analytics": "^1.6.1",
48
50
  "@vercel/speed-insights": "^1.3.1",
49
51
  "@vueuse/core": "^14.1.0",
50
52
  "@vueuse/nuxt": "^14.1.0",
51
- "ai": "^6.0.33",
53
+ "ai": "^6.0.42",
52
54
  "defu": "^6.1.4",
55
+ "dompurify": "^3.2.6",
53
56
  "exsolve": "^1.0.8",
54
57
  "git-url-parse": "^16.1.0",
58
+ "mermaid": "^11.12.2",
55
59
  "motion-v": "^1.9.0",
56
60
  "nuxt": "^4.2.2",
57
- "nuxt-component-meta": "^0.16.0",
58
- "nuxt-llms": "^0.1.3",
61
+ "nuxt-component-meta": "^0.17.1",
62
+ "nuxt-llms": "^0.2.0",
59
63
  "ohash": "^2.0.11",
60
64
  "pathe": "^2.0.3",
61
65
  "pkg-types": "^2.3.0",
62
- "prettier": "^3.7.4",
66
+ "prettier": "^3.8.0",
63
67
  "scule": "^1.3.0",
64
68
  "shiki": "^3.21.0",
65
69
  "shiki-stream": "^0.1.4",
66
70
  "shiki-transformer-color-highlight": "^1.0.0",
67
71
  "tailwindcss": "^4.1.18",
68
- "ufo": "^1.6.2",
72
+ "ufo": "^1.6.3",
69
73
  "zod": "^4.3.5"
70
74
  }
71
75
  }