docus 5.10.1 → 5.11.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/app/app.config.ts CHANGED
@@ -17,7 +17,7 @@ export default defineAppConfig({
17
17
  },
18
18
  contentToc: {
19
19
  defaultVariants: {
20
- highlight: true,
20
+ highlightVariant: 'circuit',
21
21
  },
22
22
  },
23
23
  contentNavigation: {
@@ -1,15 +1,20 @@
1
1
  <script setup lang="ts">
2
2
  import { useClipboard } from '@vueuse/core'
3
+ import { joinURL, withTrailingSlash } from 'ufo'
3
4
  import { useRuntimeConfig } from '#imports'
4
5
 
5
6
  const route = useRoute()
6
7
  const toast = useToast()
7
- const appBaseURL = useRuntimeConfig().app?.baseURL || '/'
8
+ const runtimeConfig = useRuntimeConfig()
9
+ const appBaseURL = runtimeConfig.app?.baseURL || '/'
10
+ const mcpRoute = (runtimeConfig.public.mcp as { route?: string } | undefined)?.route || '/mcp'
8
11
 
9
12
  const { copy, copied } = useClipboard()
10
13
  const { t } = useDocusI18n()
11
14
 
12
- const markdownLink = computed(() => `${window?.location?.origin}${appBaseURL}raw${route.path}.md`)
15
+ const markdownLink = computed(() => `${window?.location?.origin}${withTrailingSlash(appBaseURL)}raw${route.path}.md`)
16
+ const mcpServerUrl = computed(() => `${window?.location?.origin}${joinURL(appBaseURL, mcpRoute)}`)
17
+ const mcpDeeplink = joinURL(mcpRoute, 'deeplink')
13
18
  const items = [
14
19
  [{
15
20
  label: t('docs.copy.link'),
@@ -41,7 +46,7 @@ const items = [
41
46
  label: 'Copy MCP Server URL',
42
47
  icon: 'i-lucide-link',
43
48
  onSelect() {
44
- copy(`${window?.location?.origin}${appBaseURL}mcp`)
49
+ copy(mcpServerUrl.value)
45
50
  toast.add({
46
51
  title: 'Copied to clipboard',
47
52
  icon: 'i-lucide-check-circle',
@@ -52,7 +57,7 @@ const items = [
52
57
  label: 'Add MCP Server',
53
58
  icon: 'i-simple-icons:cursor',
54
59
  target: '_blank',
55
- to: `/mcp/deeplink`,
60
+ to: mcpDeeplink,
56
61
  },
57
62
  ],
58
63
  ]
@@ -76,6 +76,7 @@ addPrerenderPath(`/raw${route.path}.md`)
76
76
  <UPage
77
77
  v-if="page"
78
78
  :key="`page-${shouldHideToc}`"
79
+ :ui="{ root: 'lg:grid-cols-12', center: 'lg:col-span-9', right: 'lg:col-span-3' }"
79
80
  >
80
81
  <UPageHeader
81
82
  :title="page.title"
@@ -1,7 +1,7 @@
1
1
  import type { RouteLocationNormalized } from 'vue-router'
2
2
  import { consola } from 'consola'
3
3
 
4
- const log = consola.withTag('Docus')
4
+ const log = consola.withTag('docus')
5
5
 
6
6
  // Lazy import functions for locale files (bundled but not eagerly loaded)
7
7
  const localeFiles = import.meta.glob<{ default: Record<string, unknown> }>('../../i18n/locales/*.json')
@@ -21,7 +21,7 @@ export interface AssistantModuleOptions {
21
21
  model?: string
22
22
  }
23
23
 
24
- const log = logger.withTag('Docus')
24
+ const log = logger.withTag('docus')
25
25
 
26
26
  const defaults: Required<AssistantModuleOptions> = {
27
27
  apiPath: '/__docus__/assistant',
@@ -77,7 +77,9 @@ export default defineNuxtModule<AssistantModuleOptions>({
77
77
  )
78
78
 
79
79
  if (!hasAiGatewayAuth) {
80
- log.warn('AI assistant disabled: neither AI_GATEWAY_API_KEY nor VERCEL_OIDC_TOKEN found')
80
+ nuxt.hook('modules:done', () => {
81
+ log.warn('AI assistant disabled: neither `AI_GATEWAY_API_KEY` nor `VERCEL_OIDC_TOKEN` found')
82
+ })
81
83
  return
82
84
  }
83
85
 
package/modules/config.ts CHANGED
@@ -5,10 +5,11 @@ import { join } from 'node:path'
5
5
  import { inferSiteURL, getPackageJsonMetadata } from '../utils/meta'
6
6
  import { getGitBranch, getGitEnv, getLocalGitInfo } from '../utils/git'
7
7
 
8
- const log = logger.withTag('Docus')
8
+ const log = logger.withTag('docus')
9
9
 
10
10
  type I18nLocale = string | { code: string, name?: string }
11
11
  type DocusI18nOptions = { locales?: I18nLocale[], strategy?: string }
12
+ type DocusMcpOptions = { route?: string, enabled?: boolean }
12
13
  type RegisterModuleOptions = {
13
14
  langDir: string
14
15
  locales: Array<{ code: string, name: string, file: string }>
@@ -58,6 +59,16 @@ export default defineNuxtModule({
58
59
  branch: getGitBranch(),
59
60
  })
60
61
 
62
+ /*
63
+ ** MCP route (expose to client so the page header dropdown stays in sync
64
+ ** with the user-configured `mcp.route` from @nuxtjs/mcp-toolkit)
65
+ */
66
+ const mcpOptions = (nuxt.options as typeof nuxt.options & { mcp?: DocusMcpOptions }).mcp
67
+ nuxt.options.runtimeConfig.public.mcp = defu(
68
+ nuxt.options.runtimeConfig.public.mcp as DocusMcpOptions | undefined,
69
+ { route: mcpOptions?.route || '/mcp' },
70
+ )
71
+
61
72
  const forcedColorMode = (nuxt.options.appConfig.docus as Record<string, unknown>)?.colorMode as string | undefined
62
73
  if (forcedColorMode === 'light' || forcedColorMode === 'dark') {
63
74
  nuxt.options.colorMode = defu({ preference: forcedColorMode, fallback: forcedColorMode }, nuxt.options.colorMode || {}) as typeof nuxt.options.colorMode
package/modules/css.ts CHANGED
@@ -1,21 +1,37 @@
1
- import { defineNuxtModule, addTemplate, createResolver } from '@nuxt/kit'
2
- import { joinURL } from 'ufo'
1
+ import { defineNuxtModule, addTemplate, createResolver, logger } from '@nuxt/kit'
2
+ import { existsSync } from 'node:fs'
3
+ import { readFile } from 'node:fs/promises'
4
+ import { resolve } from 'pathe'
3
5
  import { resolveModulePath } from 'exsolve'
4
6
 
7
+ const log = logger.withTag('docus')
8
+
5
9
  export default defineNuxtModule({
6
10
  meta: {
7
- name: 'css',
11
+ name: 'docus-css',
8
12
  },
9
13
  async setup(_options, nuxt) {
10
- const dir = nuxt.options.rootDir
11
14
  const resolver = createResolver(import.meta.url)
12
15
 
13
- const contentDir = joinURL(dir, 'content')
16
+ const contentDir = resolve(nuxt.options.rootDir, 'content')
14
17
  const uiPath = resolveModulePath('@nuxt/ui', { from: import.meta.url, conditions: ['style'] })
15
18
  const tailwindPath = resolveModulePath('tailwindcss', { from: import.meta.url, conditions: ['style'] })
16
19
  const layerDir = resolver.resolve('../app')
17
20
  const assistantDir = resolver.resolve('../modules/assistant')
18
21
 
22
+ let userDocusPath: string | null = resolve(nuxt.options.srcDir, 'app.css')
23
+ if (existsSync(userDocusPath)) {
24
+ const userDocusCss = await readFile(userDocusPath, 'utf-8')
25
+ if (userDocusCss.includes('@import "tailwindcss"')) {
26
+ nuxt.hook('modules:done', () => {
27
+ log.warn('`app.css` contains `@import "tailwindcss";` consider removing it to avoid duplicate css.')
28
+ })
29
+ }
30
+ }
31
+ else {
32
+ userDocusPath = null
33
+ }
34
+
19
35
  const cssTemplate = addTemplate({
20
36
  filename: 'docus.css',
21
37
  getContents: () => {
@@ -25,7 +41,12 @@ export default defineNuxtModule({
25
41
  @source "${contentDir.replace(/\\/g, '/')}/**/*";
26
42
  @source "${layerDir.replace(/\\/g, '/')}/**/*";
27
43
  @source "../../app.config.ts";
28
- @source "${assistantDir.replace(/\\/g, '/')}/**/*";`
44
+ @source "${assistantDir.replace(/\\/g, '/')}/**/*";
45
+
46
+ :root {
47
+ --ui-container: 90rem;
48
+ }
49
+ ` + (userDocusPath ? `\n@import ${JSON.stringify(userDocusPath)};` : '')
29
50
  },
30
51
  })
31
52
 
@@ -2,7 +2,7 @@ import { defineNuxtModule, logger } from '@nuxt/kit'
2
2
  import { resolve } from 'node:path'
3
3
  import { readFile, writeFile } from 'node:fs/promises'
4
4
 
5
- const log = logger.withTag('Docus')
5
+ const log = logger.withTag('docus')
6
6
 
7
7
  type I18nLocale = string | { code: string }
8
8
  type DocusI18nOptions = { locales?: I18nLocale[] }
@@ -19,7 +19,7 @@ export interface SkillsModuleOptions {
19
19
  const SKILL_NAME_REGEX = /^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/
20
20
  const MAX_NAME_LENGTH = 64
21
21
 
22
- const log = logger.withTag('Docus')
22
+ const log = logger.withTag('docus')
23
23
 
24
24
  const defaults: Required<SkillsModuleOptions> = {
25
25
  dir: 'skills',
@@ -38,7 +38,9 @@ export default defineNuxtModule<SkillsModuleOptions>({
38
38
  const catalog = await scanSkills(skillsDir)
39
39
  if (!catalog.length) return
40
40
 
41
- log.info(`Found ${catalog.length} agent skill${catalog.length > 1 ? 's' : ''}: ${catalog.map(s => s.name).join(', ')}`)
41
+ nuxt.hook('modules:done', () => {
42
+ log.info(`Found ${catalog.length} agent skill${catalog.length > 1 ? 's' : ''}: ${catalog.map(s => s.name).join(', ')}`)
43
+ })
42
44
 
43
45
  nuxt.options.runtimeConfig.skills = { catalog }
44
46
 
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "docus",
3
3
  "description": "Nuxt layer for Docus documentation theme",
4
- "version": "5.10.1",
4
+ "version": "5.11.0",
5
5
  "type": "module",
6
6
  "main": "./nuxt.config.ts",
7
7
  "repository": {
@@ -50,6 +50,7 @@
50
50
  "motion-v": "^2.2.1",
51
51
  "nuxt-llms": "^0.2.0",
52
52
  "nuxt-og-image": "^6.4.5",
53
+ "pathe": "^2.0.3",
53
54
  "pkg-types": "^2.3.0",
54
55
  "scule": "^1.3.0",
55
56
  "shiki-stream": "^0.1.4",
@@ -1,4 +1,5 @@
1
1
  import { z } from 'zod'
2
+ import { joinURL } from 'ufo'
2
3
  import { queryCollection } from '@nuxt/content/server'
3
4
  import type { Collections } from '@nuxt/content'
4
5
  import { getAvailableLocales, getCollectionFromPath } from '../../utils/content'
@@ -33,11 +34,13 @@ WORKFLOW: This tool returns the complete page content including title, descripti
33
34
  cache: '1h',
34
35
  handler: async ({ path }) => {
35
36
  const event = useEvent()
36
- const config = useRuntimeConfig(event).public
37
+ const config = useRuntimeConfig(event)
38
+ const publicConfig = config.public
37
39
  const siteUrl = getRequestURL(event).origin || inferSiteURL()
40
+ const baseURL = config.app?.baseURL || '/'
38
41
 
39
- const availableLocales = getAvailableLocales(config)
40
- const collectionName = config.i18n?.locales
42
+ const availableLocales = getAvailableLocales(publicConfig)
43
+ const collectionName = publicConfig.i18n?.locales
41
44
  ? getCollectionFromPath(path, availableLocales)
42
45
  : 'docs'
43
46
 
@@ -58,7 +61,7 @@ WORKFLOW: This tool returns the complete page content including title, descripti
58
61
  path: page.path,
59
62
  description: page.description,
60
63
  content,
61
- url: `${siteUrl}${page.path}`,
64
+ url: siteUrl ? joinURL(siteUrl, baseURL, page.path) : joinURL(baseURL, page.path),
62
65
  }
63
66
  }
64
67
  catch (error) {
@@ -1,4 +1,5 @@
1
1
  import { z } from 'zod'
2
+ import { joinURL } from 'ufo'
2
3
  import { queryCollection } from '@nuxt/content/server'
3
4
  import type { Collections } from '@nuxt/content'
4
5
  import { getCollectionsToQuery, getAvailableLocales } from '../../utils/content'
@@ -39,10 +40,12 @@ OUTPUT: Returns a structured list with:
39
40
  cache: '1h',
40
41
  handler: async ({ locale }) => {
41
42
  const event = useEvent()
42
- const config = useRuntimeConfig(event).public
43
+ const config = useRuntimeConfig(event)
44
+ const publicConfig = config.public
43
45
 
44
46
  const siteUrl = getRequestURL(event).origin || inferSiteURL()
45
- const availableLocales = getAvailableLocales(config)
47
+ const baseURL = config.app?.baseURL || '/'
48
+ const availableLocales = getAvailableLocales(publicConfig)
46
49
  const collections = getCollectionsToQuery(locale, availableLocales)
47
50
 
48
51
  try {
@@ -57,7 +60,7 @@ OUTPUT: Returns a structured list with:
57
60
  path: page.path,
58
61
  description: page.description,
59
62
  locale: collectionName.replace('docs_', ''),
60
- url: `${siteUrl}${page.path}`,
63
+ url: siteUrl ? joinURL(siteUrl, baseURL, page.path) : joinURL(baseURL, page.path),
61
64
  }))
62
65
  }),
63
66
  )