undocs 0.4.11 → 0.4.12

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.
@@ -0,0 +1,70 @@
1
+ <script setup lang="ts">
2
+ import { useClipboard } from '@vueuse/core'
3
+ import { useRuntimeConfig } from '#imports'
4
+
5
+ const route = useRoute()
6
+ const appBaseURL = useRuntimeConfig().app?.baseURL || '/'
7
+
8
+ const { copy, copied } = useClipboard()
9
+
10
+ const markdownLink = computed(() => `${window?.location?.origin}${appBaseURL}raw${route.path}.md`)
11
+ const items = [
12
+ {
13
+ label: 'Copy Markdown Link',
14
+ icon: 'i-lucide-link',
15
+ onSelect() {
16
+ copy(markdownLink.value)
17
+ },
18
+ },
19
+ {
20
+ label: 'View as Markdown',
21
+ icon: 'i-simple-icons:markdown',
22
+ target: '_blank',
23
+ to: markdownLink.value,
24
+ },
25
+ {
26
+ label: 'Open in ChatGPT',
27
+ icon: 'i-simple-icons:openai',
28
+ target: '_blank',
29
+ to: `https://chatgpt.com/?hints=search&q=${encodeURIComponent(`Read ${markdownLink.value} so I can ask questions about it.`)}`,
30
+ },
31
+ {
32
+ label: 'Open in Claude',
33
+ icon: 'i-simple-icons:anthropic',
34
+ target: '_blank',
35
+ to: `https://claude.ai/new?q=${encodeURIComponent(`Read ${markdownLink.value} so I can ask questions about it.`)}`,
36
+ },
37
+ ]
38
+
39
+ async function copyPage() {
40
+ const page = await $fetch<string>(`/raw${route.path}.md`)
41
+ copy(page)
42
+ }
43
+ </script>
44
+
45
+ <template>
46
+ <UFieldGroup size="sm">
47
+ <UButton
48
+ label="Copy Page"
49
+ :icon="copied ? 'i-lucide-check' : 'i-lucide-copy'"
50
+ color="neutral"
51
+ variant="soft"
52
+ :ui="{
53
+ leadingIcon: 'text-neutral size-3.5',
54
+ }"
55
+ @click="copyPage"
56
+ />
57
+
58
+ <UDropdownMenu
59
+ size="sm"
60
+ :items="items"
61
+ :content="{
62
+ align: 'end',
63
+ side: 'bottom',
64
+ sideOffset: 8,
65
+ }"
66
+ >
67
+ <UButton icon="i-lucide-chevron-down" color="neutral" variant="soft" class="border-l border-muted" />
68
+ </UDropdownMenu>
69
+ </UFieldGroup>
70
+ </template>
@@ -116,8 +116,8 @@ function transformMermaid(node: MinimarkNode) {
116
116
  node[0] = 'mermaid'
117
117
  // @ts-expect-error
118
118
  node[1] = { code: node[1].code || '' }
119
- // @ts-expect-error
120
- node[2] = []
119
+ // Remove all children (splice instead of node[2] = [] which would create an invalid empty array child)
120
+ node.splice(2)
121
121
  }
122
122
  }
123
123
 
@@ -0,0 +1,32 @@
1
+ import type { NitroModule } from 'nitropack'
2
+ import { resolve } from 'node:path'
3
+ import { readFile, writeFile } from 'node:fs/promises'
4
+
5
+ import { defineNuxtModule } from 'nuxt/kit'
6
+
7
+ export default defineNuxtModule((_options, nuxt) => {
8
+ nuxt.options.nitro.modules ??= []
9
+ nuxt.options.nitro.modules.push(mdRewrite())
10
+ })
11
+
12
+ function mdRewrite(): NitroModule {
13
+ return {
14
+ name: 'markdown-rewrite',
15
+ setup(nitro) {
16
+ if (nitro.options.dev || !nitro.options.preset.includes('vercel')) {
17
+ return
18
+ }
19
+ nitro.hooks.hook('compiled', async () => {
20
+ const vcJSON = resolve(nitro.options.output.dir, 'config.json')
21
+ const vcConfig = JSON.parse(await readFile(vcJSON, 'utf8'))
22
+ vcConfig.routes.unshift({
23
+ src: '^/(.*)$',
24
+ dest: '/raw/$1.md',
25
+ has: [{ type: 'header', key: 'accept', value: '(.*)text/markdown(.*)' }],
26
+ check: true,
27
+ })
28
+ await writeFile(vcJSON, JSON.stringify(vcConfig, null, 2), 'utf8')
29
+ })
30
+ },
31
+ }
32
+ }
@@ -16,7 +16,7 @@ export default defineNuxtConfig({
16
16
  name: 'undocs',
17
17
  },
18
18
  ssr,
19
- modules: ['@nuxt/ui', '@nuxt/content', isProd && '@nuxtjs/plausible'],
19
+ modules: ['@nuxt/ui', '@nuxt/content', isProd && '@nuxtjs/plausible', 'nuxt-llms'],
20
20
  css: [resolve('./assets/main.css')],
21
21
  ui: {
22
22
  theme: {
@@ -1,4 +1,5 @@
1
1
  <script setup lang="ts">
2
+ import { joinURL } from 'ufo'
2
3
  import { kebabCase } from 'scule'
3
4
  import type { ContentNavigationItem } from '@nuxt/content'
4
5
 
@@ -56,14 +57,37 @@ usePageSEO({
56
57
  ogTitle: page.value?.title,
57
58
  description: page.value?.description,
58
59
  })
60
+
61
+ const path = computed(() => route.path.replace(/\/$/, ''))
62
+ prerenderRoutes([joinURL('/raw', `${path.value}.md`)])
63
+ useHead({
64
+ link: [
65
+ {
66
+ rel: 'alternate',
67
+ href: joinURL(appConfig.site.url, 'raw', `${path.value}.md`),
68
+ type: 'text/markdown',
69
+ },
70
+ ],
71
+ })
59
72
  </script>
60
73
 
61
74
  <template>
62
75
  <UPage v-if="page">
63
- <UPageHeader :title="page.title" :description="page.description" :links="page.links">
76
+ <UPageHeader
77
+ :title="page.title"
78
+ :description="page.description"
79
+ :ui="{
80
+ wrapper: 'flex-row items-center flex-wrap justify-between',
81
+ }"
82
+ >
64
83
  <template #headline>
65
84
  <UBreadcrumb :items="breadcrumb" />
66
85
  </template>
86
+ <template #links>
87
+ <UButton v-for="(link, index) in page.links" :key="index" size="sm" v-bind="link" />
88
+
89
+ <PageHeaderLinks />
90
+ </template>
67
91
  </UPageHeader>
68
92
 
69
93
  <template v-if="page.body?.toc?.links?.length" #right>
@@ -0,0 +1,26 @@
1
+ import { queryCollection } from '@nuxt/content/server'
2
+ import { stringify } from 'minimark/stringify'
3
+ import { withLeadingSlash } from 'ufo'
4
+
5
+ export default eventHandler(async (event) => {
6
+ const slug = getRouterParams(event)['slug.md']
7
+ if (!slug?.endsWith('.md')) {
8
+ throw createError({ statusCode: 404, statusMessage: 'Page not found', fatal: true })
9
+ }
10
+
11
+ const path = withLeadingSlash(slug.replace('.md', ''))
12
+
13
+ const page = await queryCollection(event, 'content').path(path).first()
14
+ if (!page) {
15
+ throw createError({ statusCode: 404, statusMessage: 'Page not found', fatal: true })
16
+ }
17
+
18
+ // Add title and description to the top of the page if missing
19
+ if (page.body.value[0]?.[0] !== 'h1') {
20
+ page.body.value.unshift(['blockquote', {}, page.description])
21
+ page.body.value.unshift(['h1', {}, page.title])
22
+ }
23
+
24
+ setHeader(event, 'Content-Type', 'text/markdown; charset=utf-8')
25
+ return stringify({ ...page.body, type: 'minimark' }, { format: 'markdown/html' })
26
+ })
package/cli/cli.mjs CHANGED
@@ -58,8 +58,11 @@ export function createCLI(opts) {
58
58
  process.chdir(appDir)
59
59
  process.on('exit', () => unwatch())
60
60
 
61
- const { runCommand } = await import('nuxi')
62
- await runCommand('dev', [appDir, '--no-fork', '--port', process.env.PORT || '4000'], { overrides: nuxtConfig })
61
+ const { runCommand, main } = await import('nuxi')
62
+ const cmd = await main.subCommands.dev()
63
+ await runCommand(cmd, [appDir, '--no-fork', '--port', process.env.PORT || '4000'], {
64
+ overrides: nuxtConfig,
65
+ })
63
66
  },
64
67
  })
65
68
 
@@ -74,8 +77,9 @@ export function createCLI(opts) {
74
77
 
75
78
  process.chdir(appDir)
76
79
 
77
- const { runCommand } = await import('nuxi')
78
- await runCommand('generate', [appDir], { overrides: nuxtConfig })
80
+ const { runCommand, main } = await import('nuxi')
81
+ const cmd = await main.subCommands.generate()
82
+ await runCommand(cmd, [appDir], { overrides: nuxtConfig })
79
83
  },
80
84
  })
81
85
 
package/cli/setup.mjs CHANGED
@@ -3,6 +3,7 @@ import { resolve } from 'node:path'
3
3
  import { existsSync } from 'node:fs'
4
4
  import { execSync } from 'node:child_process'
5
5
  import { loadConfig, watchConfig } from 'c12'
6
+ import { defu } from 'defu'
6
7
 
7
8
  const appDir = fileURLToPath(new URL('../app', import.meta.url))
8
9
 
@@ -80,6 +81,16 @@ export async function setupDocs(docsDir, opts = {}) {
80
81
  }
81
82
 
82
83
  // Prepare loadNuxt overrides
84
+ const llmsConfig = defu(docsconfig.llms, {
85
+ domain: docsconfig.url,
86
+ title: docsconfig.name || '',
87
+ description: docsconfig.description || '',
88
+ full: {
89
+ title: docsconfig.name || '',
90
+ description: docsconfig.description || '',
91
+ },
92
+ })
93
+
83
94
  const nuxtConfig = {
84
95
  compatibilityDate: 'latest',
85
96
  rootDir: docsSrcDir,
@@ -95,6 +106,8 @@ export async function setupDocs(docsDir, opts = {}) {
95
106
  description: docsconfig.description || '',
96
107
  url: docsconfig.url,
97
108
  },
109
+ // @ts-ignore
110
+ llms: llmsConfig,
98
111
  appConfig: {
99
112
  site: {
100
113
  name: docsconfig.name || '',
@@ -120,6 +133,9 @@ export async function setupDocs(docsDir, opts = {}) {
120
133
  dir: resolve(docsDir, '.docs/public'),
121
134
  },
122
135
  ],
136
+ prerender: {
137
+ routes: ['/llms.txt', '/llms-full.txt'],
138
+ },
123
139
  },
124
140
  routeRules: {
125
141
  ...Object.fromEntries(Object.entries(docsconfig.redirects || {}).map(([from, to]) => [from, { redirect: to }])),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "undocs",
3
- "version": "0.4.11",
3
+ "version": "0.4.12",
4
4
  "repository": "unjs/undocs",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -31,47 +31,47 @@
31
31
  },
32
32
  "dependencies": {
33
33
  "@headlessui/vue": "^1.7.23",
34
- "@iconify-json/logos": "^1.2.9",
35
- "@iconify-json/simple-icons": "^1.2.52",
36
- "@nuxt/content": "^3.7.1",
37
- "@nuxt/fonts": "^0.11.4",
38
- "@nuxt/ui": "4.0.0-alpha.2",
34
+ "@iconify-json/logos": "^1.2.10",
35
+ "@iconify-json/simple-icons": "^1.2.66",
36
+ "@nuxt/content": "^3.10.0",
37
+ "@nuxt/fonts": "^0.12.1",
38
+ "@nuxt/ui": "4.2.0",
39
39
  "@nuxtjs/plausible": "^2.0.1",
40
40
  "@resvg/resvg-wasm": "^2.6.2",
41
41
  "automd": "^0.4.2",
42
- "c12": "^3.3.0",
42
+ "c12": "^3.3.3",
43
43
  "citty": "^0.1.6",
44
44
  "consola": "^3.4.2",
45
45
  "defu": "^6.1.4",
46
46
  "is-buffer": "^2.0.5",
47
47
  "md4w": "^0.2.7",
48
- "mermaid": "^11.12.0",
49
- "motion-v": "^1.7.1",
50
- "nitropack": "^2.12.6",
51
- "nuxi": "^3.28.0",
52
- "nuxt": "^4.1.2",
48
+ "mermaid": "^11.12.2",
49
+ "motion-v": "^1.9.0",
50
+ "nitropack": "^2.13.1",
51
+ "nuxi": "^3.32.0",
52
+ "nuxt": "^4.2.2",
53
53
  "nuxt-build-cache": "^0.1.1",
54
54
  "nuxt-llms": "^0.1.3",
55
55
  "pkg-types": "^2.3.0",
56
56
  "scule": "^1.3.0",
57
- "shiki": "^3.12.2",
58
- "tailwindcss": "^4.1.13",
59
- "unctx": "^2.4.1",
60
- "unstorage": "^1.17.1",
61
- "vue": "^3.5.21",
62
- "vue-router": "^4.5.1"
57
+ "shiki": "^3.21.0",
58
+ "tailwindcss": "^4.1.18",
59
+ "unctx": "^2.5.0",
60
+ "unstorage": "^1.17.4",
61
+ "vue": "^3.5.26",
62
+ "vue-router": "^4.6.4"
63
63
  },
64
64
  "devDependencies": {
65
- "@nuxt/eslint-config": "^1.9.0",
66
- "@nuxt/image": "^1.11.0",
67
- "@types/node": "^24.5.2",
65
+ "@nuxt/eslint-config": "^1.12.1",
66
+ "@nuxt/image": "^2.0.0",
67
+ "@types/node": "^25.0.8",
68
68
  "changelogen": "^0.6.2",
69
- "eslint": "^9.35.0",
70
- "eslint-config-unjs": "^0.5.0",
71
- "jiti": "^2.5.1",
72
- "prettier": "^3.6.2",
73
- "typescript": "^5.9.2",
74
- "vue-tsc": "^3.0.7"
69
+ "eslint": "^9.39.2",
70
+ "eslint-config-unjs": "^0.6.2",
71
+ "jiti": "^2.6.1",
72
+ "prettier": "^3.8.0",
73
+ "typescript": "^5.9.3",
74
+ "vue-tsc": "^3.2.2"
75
75
  },
76
- "packageManager": "pnpm@10.17.0"
76
+ "packageManager": "pnpm@10.28.0"
77
77
  }
@@ -8,6 +8,12 @@ export interface DocsConfig {
8
8
  url?: string
9
9
  github?: string
10
10
  socials?: Record<string, string>
11
+ llms?: {
12
+ full?: {
13
+ title?: string
14
+ description?: string
15
+ }
16
+ }
11
17
  branch?: string
12
18
  banner?: BannerProps
13
19
  versions?: { label: string; to: string; active?: boolean }[]
@@ -35,6 +35,40 @@
35
35
  "type": "string",
36
36
  "description": "The GitHub repository for the documentation site."
37
37
  },
38
+ "llms": {
39
+ "type": "object",
40
+ "description": "Configuration for `nuxt-llms` generation (`/llms.txt` and `/llms-full.txt`).",
41
+ "additionalProperties": false,
42
+ "properties": {
43
+ "domain": {
44
+ "type": "string",
45
+ "description": "Public site domain (e.g. https://example.com)."
46
+ },
47
+ "title": {
48
+ "type": "string",
49
+ "description": "Title used in `/llms.txt`."
50
+ },
51
+ "description": {
52
+ "type": "string",
53
+ "description": "Description used in `/llms.txt`."
54
+ },
55
+ "full": {
56
+ "type": "object",
57
+ "description": "Metadata for `/llms-full.txt`.",
58
+ "additionalProperties": false,
59
+ "properties": {
60
+ "title": {
61
+ "type": "string",
62
+ "description": "Title used in `/llms-full.txt`."
63
+ },
64
+ "description": {
65
+ "type": "string",
66
+ "description": "Description used in `/llms-full.txt`."
67
+ }
68
+ }
69
+ }
70
+ }
71
+ },
38
72
  "socials": {
39
73
  "type": "object",
40
74
  "description": "Social media links for the documentation site.",