@lupinum/ginko-docs 0.4.0-rc.9 → 0.5.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/CHANGELOG.md +141 -0
- package/LICENSE +1 -1
- package/README.md +300 -53
- package/app/app.config.ts +44 -55
- package/app/assets/docs.css +619 -0
- package/app/components/DocsBanner.vue +29 -0
- package/app/components/DocsExample.vue +43 -0
- package/app/components/DocsFooter.vue +19 -0
- package/app/components/DocsHeader.vue +113 -0
- package/app/components/DocsIcon.vue +12 -0
- package/app/components/DocsLanding.vue +58 -0
- package/app/components/DocsLanguage.vue +40 -0
- package/app/components/DocsNavSheet.vue +37 -0
- package/app/components/DocsNavTree.vue +38 -0
- package/app/components/DocsPage.vue +88 -0
- package/app/components/DocsPageActions.vue +47 -0
- package/app/components/DocsPageEnd.ts +12 -0
- package/app/components/DocsPost.vue +58 -0
- package/app/components/DocsSearch.vue +136 -0
- package/app/components/DocsShell.vue +36 -0
- package/app/components/DocsSidebar.vue +19 -0
- package/app/components/DocsSidebarEnd.ts +4 -0
- package/app/components/DocsTable.vue +11 -0
- package/app/components/DocsThemeToggle.vue +45 -0
- package/app/components/DocsToc.vue +73 -0
- package/app/composables/useDocs.ts +53 -0
- package/app/composables/useDocsBlog.ts +33 -0
- package/app/composables/useDocsCopy.ts +17 -0
- package/app/composables/useDocsFrame.ts +11 -0
- package/app/composables/useDocsPopover.ts +36 -0
- package/app/composables/useDocsSite.ts +31 -0
- package/app/docs-pages/blog.vue +55 -0
- package/app/docs-pages/changelog.vue +35 -0
- package/app/docs-pages/doc.vue +16 -0
- package/app/docs-pages/docs.vue +9 -0
- package/app/docs-pages/index.vue +12 -0
- package/app/docs-pages/post.vue +8 -0
- package/app/error.vue +38 -115
- package/app/layouts/default.vue +29 -7
- package/app/lib/blogTypes.ts +8 -0
- package/app/lib/docsFirst.ts +11 -0
- package/app/lib/docsInit.ts +18 -0
- package/app/lib/docsNav.ts +36 -0
- package/app/lib/docsPage.ts +84 -0
- package/app/lib/markdownComponents.ts +15 -0
- package/app/lib/previewToc.ts +18 -0
- package/dist/config.d.mts +252 -0
- package/dist/config.d.ts +252 -0
- package/dist/config.mjs +83 -0
- package/dist/icons.d.mts +38 -0
- package/dist/icons.d.ts +38 -0
- package/dist/icons.mjs +175 -0
- package/dist/pages.d.mts +5 -0
- package/dist/pages.d.ts +5 -0
- package/dist/pages.mjs +68 -0
- package/i18n/de.json +1 -0
- package/i18n/en.json +1 -0
- package/i18n/fr.json +1 -0
- package/icons/README.md +2 -0
- package/icons/bluesky.svg +1 -0
- package/icons/claude.svg +1 -0
- package/icons/discord.svg +1 -0
- package/icons/github.svg +1 -0
- package/icons/linkedin.svg +1 -0
- package/icons/mastodon.svg +1 -0
- package/icons/openai.svg +1 -0
- package/icons/x.svg +1 -0
- package/icons/youtube.svg +1 -0
- package/nuxt.config.ts +22 -212
- package/package.json +44 -76
- package/public/_docs/fonts/LICENSE +188 -0
- package/public/_docs/fonts/inter-latin-wght-normal.woff2 +0 -0
- package/public/_docs/fonts/jetbrains-mono-latin-400-normal.woff2 +0 -0
- package/public/_docs/fonts/jetbrains-mono-latin-500-normal.woff2 +0 -0
- package/server/feeds/blog-locale.get.ts +9 -0
- package/server/feeds/blog.get.ts +4 -0
- package/server/lib/blogFeed.ts +35 -0
- package/server/mcp/handler.ts +108 -0
- package/server/routes/__ginko/docs-mcp.ts +35 -0
- package/shared/layerRoutes.ts +66 -0
- package/shared/paths.ts +4 -0
- package/app/app.vue +0 -98
- package/app/assets/css/prose.css +0 -3240
- package/app/assets/css/tailwind.css +0 -394
- package/app/assets/css/theme-palettes.css +0 -309
- package/app/assets/css/theme-presets.css +0 -141
- package/app/components/OgImage/GinkoDocs.satori.vue +0 -238
- package/app/components/content/DocumentPageShell.vue +0 -55
- package/app/components/content/Feedback.vue +0 -102
- package/app/components/content/ImageZoomDialog.vue +0 -84
- package/app/components/content/PageMarkdownCopy.vue +0 -104
- package/app/components/content/imageZoom.ts +0 -25
- package/app/components/mdc/MdcAccordion.vue +0 -67
- package/app/components/mdc/MdcAccordionItem.vue +0 -42
- package/app/components/mdc/MdcApi.vue +0 -159
- package/app/components/mdc/MdcAside.vue +0 -23
- package/app/components/mdc/MdcCallout.vue +0 -61
- package/app/components/mdc/MdcCard.vue +0 -175
- package/app/components/mdc/MdcCards.vue +0 -30
- package/app/components/mdc/MdcCenter.vue +0 -71
- package/app/components/mdc/MdcCodeGroup.vue +0 -197
- package/app/components/mdc/MdcCodeTree.vue +0 -234
- package/app/components/mdc/MdcCollapse.vue +0 -61
- package/app/components/mdc/MdcColumn.vue +0 -70
- package/app/components/mdc/MdcDropcap.vue +0 -84
- package/app/components/mdc/MdcError.vue +0 -23
- package/app/components/mdc/MdcExcerpt.vue +0 -26
- package/app/components/mdc/MdcFigure.vue +0 -85
- package/app/components/mdc/MdcFiles.vue +0 -120
- package/app/components/mdc/MdcIdea.vue +0 -17
- package/app/components/mdc/MdcInfo.vue +0 -17
- package/app/components/mdc/MdcInlineToc.vue +0 -51
- package/app/components/mdc/MdcKbd.vue +0 -10
- package/app/components/mdc/MdcLayout.vue +0 -57
- package/app/components/mdc/MdcNote.vue +0 -17
- package/app/components/mdc/MdcQuiz.vue +0 -241
- package/app/components/mdc/MdcQuizQuestion.vue +0 -221
- package/app/components/mdc/MdcReadMore.vue +0 -70
- package/app/components/mdc/MdcSteps.vue +0 -59
- package/app/components/mdc/MdcSuccess.vue +0 -23
- package/app/components/mdc/MdcTab.vue +0 -17
- package/app/components/mdc/MdcTabs.vue +0 -150
- package/app/components/mdc/MdcTimeline.vue +0 -17
- package/app/components/mdc/MdcTimelineItem.vue +0 -40
- package/app/components/mdc/MdcWarning.vue +0 -23
- package/app/components/mdc/api.utils.ts +0 -132
- package/app/components/mdc/code-tree.utils.ts +0 -22
- package/app/components/mdc/icons.ts +0 -10
- package/app/components/mdc/quiz.utils.ts +0 -38
- package/app/components/prose/ProseA.vue +0 -27
- package/app/components/prose/ProseBlockquote.vue +0 -30
- package/app/components/prose/ProseCode.vue +0 -14
- package/app/components/prose/ProseEm.vue +0 -14
- package/app/components/prose/ProseH1.vue +0 -18
- package/app/components/prose/ProseH2.vue +0 -29
- package/app/components/prose/ProseH3.vue +0 -29
- package/app/components/prose/ProseH4.vue +0 -29
- package/app/components/prose/ProseH5.vue +0 -15
- package/app/components/prose/ProseH6.vue +0 -15
- package/app/components/prose/ProseHr.vue +0 -12
- package/app/components/prose/ProseImg.vue +0 -55
- package/app/components/prose/ProseLi.vue +0 -14
- package/app/components/prose/ProseOl.vue +0 -14
- package/app/components/prose/ProseP.vue +0 -14
- package/app/components/prose/ProsePre.vue +0 -180
- package/app/components/prose/ProseStrong.vue +0 -14
- package/app/components/prose/ProseTable.vue +0 -18
- package/app/components/prose/ProseTbody.vue +0 -14
- package/app/components/prose/ProseTd.vue +0 -14
- package/app/components/prose/ProseTh.vue +0 -14
- package/app/components/prose/ProseThead.vue +0 -14
- package/app/components/prose/ProseTr.vue +0 -14
- package/app/components/prose/ProseUl.vue +0 -14
- package/app/components/site/ModeToggle.vue +0 -81
- package/app/components/site/SiteBanner.vue +0 -97
- package/app/components/site/SiteFooter.vue +0 -90
- package/app/components/site/SiteHeader.vue +0 -371
- package/app/components/site/SiteHeroCode.vue +0 -173
- package/app/components/site/SiteInteractionLayer.vue +0 -42
- package/app/components/site/SiteLocaleSwitcher.vue +0 -190
- package/app/components/site/SiteLogoMark.vue +0 -12
- package/app/components/site/SiteSkipLink.vue +0 -14
- package/app/components/site/SiteSocialLinks.vue +0 -33
- package/app/components/site/header-utils.ts +0 -3
- package/app/components/site/mode-toggle.utils.ts +0 -18
- package/app/components/ui/badge/Badge.vue +0 -28
- package/app/components/ui/badge/index.ts +0 -24
- package/app/components/ui/button/Button.vue +0 -31
- package/app/components/ui/button/index.ts +0 -35
- package/app/components/ui/collapsible/Collapsible.vue +0 -15
- package/app/components/ui/collapsible/CollapsibleContent.vue +0 -25
- package/app/components/ui/collapsible/CollapsibleTrigger.vue +0 -12
- package/app/components/ui/collapsible/index.ts +0 -3
- package/app/components/ui/dropdown-menu/DropdownMenu.vue +0 -15
- package/app/components/ui/dropdown-menu/DropdownMenuContent.vue +0 -50
- package/app/components/ui/dropdown-menu/DropdownMenuItem.vue +0 -41
- package/app/components/ui/dropdown-menu/DropdownMenuRadioGroup.vue +0 -15
- package/app/components/ui/dropdown-menu/DropdownMenuRadioItem.vue +0 -37
- package/app/components/ui/dropdown-menu/DropdownMenuSeparator.vue +0 -23
- package/app/components/ui/dropdown-menu/DropdownMenuTrigger.vue +0 -14
- package/app/components/ui/dropdown-menu/index.ts +0 -7
- package/app/components/ui/kbd/Kbd.vue +0 -23
- package/app/components/ui/kbd/index.ts +0 -21
- package/app/components/ui/scroll-area/ScrollArea.vue +0 -39
- package/app/components/ui/scroll-area/ScrollBar.vue +0 -33
- package/app/components/ui/scroll-area/index.ts +0 -2
- package/app/components/ui/separator/Separator.vue +0 -27
- package/app/components/ui/separator/index.ts +0 -1
- package/app/components/ui/sheet/Sheet.vue +0 -15
- package/app/components/ui/sheet/SheetContent.vue +0 -59
- package/app/components/ui/sheet/SheetDescription.vue +0 -21
- package/app/components/ui/sheet/SheetOverlay.vue +0 -26
- package/app/components/ui/sheet/SheetTitle.vue +0 -21
- package/app/components/ui/sheet/SheetTrigger.vue +0 -12
- package/app/components/ui/sheet/index.ts +0 -5
- package/app/components/ui/tabs/Tabs.vue +0 -24
- package/app/components/ui/tabs/TabsList.vue +0 -26
- package/app/components/ui/tabs/TabsTrigger.vue +0 -28
- package/app/components/ui/tabs/index.ts +0 -3
- package/app/composables/site-navigation.utils.ts +0 -133
- package/app/composables/useCanonicalUrl.ts +0 -9
- package/app/composables/useContentRouteAlternates.ts +0 -35
- package/app/composables/useGinkoAnalytics.ts +0 -52
- package/app/composables/useGinkoDocsConfig.ts +0 -6
- package/app/composables/useGinkoOgImage.ts +0 -20
- package/app/composables/useLocalizedPath.ts +0 -12
- package/app/composables/useLocalizedRouteSwitch.ts +0 -31
- package/app/composables/useMetaKey.ts +0 -18
- package/app/composables/usePageMarkdownActions.ts +0 -79
- package/app/composables/useProseAppearance.ts +0 -24
- package/app/composables/useSchemaJsonLd.ts +0 -25
- package/app/composables/useSiteNavigation.ts +0 -64
- package/app/config/site.utils.ts +0 -36
- package/app/features/docs/components/DocsBreadcrumb.vue +0 -37
- package/app/features/docs/components/DocsContributeLinks.vue +0 -75
- package/app/features/docs/components/DocsMobileToc.vue +0 -108
- package/app/features/docs/components/DocsPageContent.vue +0 -234
- package/app/features/docs/components/DocsPageNav.vue +0 -57
- package/app/features/docs/components/DocsSidebar.vue +0 -145
- package/app/features/docs/components/DocsSidebarDropdown.vue +0 -70
- package/app/features/docs/components/DocsSidebarItem.vue +0 -122
- package/app/features/docs/components/DocsSidebarList.vue +0 -35
- package/app/features/docs/components/DocsSidebarRow.vue +0 -27
- package/app/features/docs/components/DocsSidebarTabs.vue +0 -59
- package/app/features/docs/components/DocsToc.vue +0 -106
- package/app/features/docs/composables/useDocsNavigation.ts +0 -29
- package/app/features/docs/composables/useDocsNavigationData.ts +0 -18
- package/app/features/docs/composables/useRevealActive.ts +0 -58
- package/app/features/docs/composables/useScrollspy.ts +0 -135
- package/app/features/docs/docs-navigation.ts +0 -157
- package/app/features/docs/toc-context.ts +0 -6
- package/app/features/search/command-center.ts +0 -110
- package/app/features/search/components/SiteCommandCenter.vue +0 -359
- package/app/features/search/components/SiteSearchPageHighlight.vue +0 -126
- package/app/features/search/useCommandCenter.ts +0 -292
- package/app/layouts/blog.vue +0 -15
- package/app/layouts/docs.vue +0 -20
- package/app/lib/errors.ts +0 -37
- package/app/lib/schema-org.ts +0 -38
- package/app/pages/blog/[slug].vue +0 -228
- package/app/pages/blog/index.vue +0 -130
- package/app/pages/docs/[...slug].vue +0 -10
- package/app/pages/docs/index.vue +0 -19
- package/app/pages/index.vue +0 -278
- package/app/plugins/ginko-docs-theme.ts +0 -14
- package/app/plugins/ssr-width.ts +0 -6
- package/app/utils/content.ts +0 -67
- package/app/utils/file-icons.ts +0 -90
- package/app/utils/index.ts +0 -7
- package/app/utils/repository.ts +0 -20
- package/components.ts +0 -9
- package/content-collections.ts +0 -89
- package/content.js +0 -200
- package/content.ts +0 -104
- package/i18n/i18n.config.ts +0 -10
- package/i18n/locales.ts +0 -81
- package/i18n/messages/global/banner.ts +0 -8
- package/i18n/messages/global/blog.ts +0 -18
- package/i18n/messages/global/command.ts +0 -49
- package/i18n/messages/global/docs.ts +0 -36
- package/i18n/messages/global/errors.ts +0 -36
- package/i18n/messages/global/feedback.ts +0 -11
- package/i18n/messages/global/index.ts +0 -21
- package/i18n/messages/global/nav.ts +0 -26
- package/i18n/messages/global/site.ts +0 -4
- package/i18n/messages/global/theme.ts +0 -9
- package/i18n/messages/index.ts +0 -20
- package/i18n/messages/localize.ts +0 -35
- package/i18n/routes.ts +0 -33
- package/icon-bundle.ts +0 -116
- package/index.d.ts +0 -37
- package/modules/analytics-boundary.ts +0 -23
- package/modules/feature-routing.ts +0 -68
- package/modules/syntax-highlighting.ts +0 -120
- package/public/fonts/public-sans/LICENSE +0 -93
- package/public/fonts/public-sans/README.md +0 -12
- package/public/fonts/public-sans/public-sans-400-normal-latin.woff +0 -0
- package/public/fonts/public-sans/public-sans-500-normal-latin.woff +0 -0
- package/public/fonts/public-sans/public-sans-600-normal-latin.woff +0 -0
- package/public/fonts/public-sans/public-sans-700-normal-latin.woff +0 -0
- package/runtime/generate-favicons.mjs +0 -124
- package/runtime/server/agent-markdown.ts +0 -19
- package/server/api/_ginko-docs/redirects.get.ts +0 -28
- package/server/mcp/tools/get-page.ts +0 -14
- package/server/mcp/tools/list-pages.ts +0 -10
- package/server/middleware/redirects.ts +0 -22
- package/server/plugins/agent-markdown.ts +0 -6
- package/server/plugins/sitemap-policy.ts +0 -12
- package/server/routes/.well-known/ai-catalog.json.get.ts +0 -10
- package/server/routes/[locale]/blog/rss.xml.get.ts +0 -13
- package/server/routes/blog/rss.xml.get.ts +0 -5
- package/server/routes/mcp/server-card.get.ts +0 -14
- package/server/utils/blog-feed.ts +0 -51
- package/server/utils/feed.ts +0 -70
- package/server/utils/mcp-server-card.ts +0 -45
- package/server/utils/redirects.ts +0 -38
- package/server/utils/redirects.utils.ts +0 -58
- package/server/utils/sitemap.ts +0 -45
- package/shared/route-slugs.ts +0 -5
- package/shared/theme-palettes.ts +0 -37
- package/shared/theme-presets.ts +0 -3
- package/shared/types/app-config.ts +0 -172
- package/shared/types/nuxt-config.ts +0 -14
- package/tags.ts +0 -192
package/package.json
CHANGED
|
@@ -1,103 +1,71 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lupinum/ginko-docs",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
"content",
|
|
7
|
-
"documentation",
|
|
8
|
-
"i18n",
|
|
9
|
-
"markdown",
|
|
10
|
-
"nuxt"
|
|
11
|
-
],
|
|
12
|
-
"homepage": "https://ginko-docs.lupinum.com",
|
|
13
|
-
"bugs": {
|
|
14
|
-
"url": "https://github.com/lupinum-dev/ginko-docs/issues"
|
|
15
|
-
},
|
|
3
|
+
"version": "0.5.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "Docs sites on Ginko: a Nuxt layer with navigation, search, blog, changelog and Markdown components.",
|
|
16
6
|
"license": "MIT",
|
|
17
7
|
"author": "Lupinum OG <info@lupinum.com> (https://lupinum.com)",
|
|
18
8
|
"repository": {
|
|
19
9
|
"type": "git",
|
|
20
|
-
"url": "git+https://github.com/lupinum-dev/ginko
|
|
21
|
-
"directory": "
|
|
10
|
+
"url": "git+https://github.com/lupinum-dev/ginko.git",
|
|
11
|
+
"directory": "packages/ginko-docs"
|
|
12
|
+
},
|
|
13
|
+
"homepage": "https://github.com/lupinum-dev/ginko/tree/main/packages/ginko-docs#readme",
|
|
14
|
+
"bugs": {
|
|
15
|
+
"url": "https://github.com/lupinum-dev/ginko/issues"
|
|
22
16
|
},
|
|
23
|
-
"
|
|
24
|
-
"
|
|
17
|
+
"engines": {
|
|
18
|
+
"node": "^22.18.0 || ^24.0.0 || ^26.0.0"
|
|
25
19
|
},
|
|
26
20
|
"files": [
|
|
21
|
+
"nuxt.config.ts",
|
|
27
22
|
"app",
|
|
28
|
-
"i18n",
|
|
29
|
-
"modules",
|
|
30
|
-
"public",
|
|
31
|
-
"runtime",
|
|
32
23
|
"server",
|
|
33
24
|
"shared",
|
|
34
|
-
"
|
|
35
|
-
"
|
|
36
|
-
"
|
|
37
|
-
"
|
|
38
|
-
"
|
|
39
|
-
"index.d.ts",
|
|
40
|
-
"nuxt.config.ts",
|
|
41
|
-
"tags.ts",
|
|
42
|
-
"!**/*.test.ts"
|
|
25
|
+
"icons",
|
|
26
|
+
"public",
|
|
27
|
+
"i18n",
|
|
28
|
+
"dist",
|
|
29
|
+
"CHANGELOG.md"
|
|
43
30
|
],
|
|
44
|
-
"type": "module",
|
|
45
31
|
"main": "./nuxt.config.ts",
|
|
46
32
|
"exports": {
|
|
47
33
|
".": "./nuxt.config.ts",
|
|
48
|
-
"./
|
|
49
|
-
"types": "./
|
|
50
|
-
"import": "./
|
|
51
|
-
"default": "./content.js"
|
|
34
|
+
"./config": {
|
|
35
|
+
"types": "./dist/config.d.mts",
|
|
36
|
+
"import": "./dist/config.mjs"
|
|
52
37
|
},
|
|
53
|
-
"./
|
|
54
|
-
"./
|
|
55
|
-
"./nuxt-config": "./shared/types/nuxt-config.ts"
|
|
38
|
+
"./pages": "./dist/pages.mjs",
|
|
39
|
+
"./icons": "./dist/icons.mjs"
|
|
56
40
|
},
|
|
57
41
|
"publishConfig": {
|
|
58
42
|
"access": "public"
|
|
59
43
|
},
|
|
60
44
|
"dependencies": {
|
|
61
|
-
"@
|
|
62
|
-
"@iconify-json/
|
|
63
|
-
"@iconify
|
|
64
|
-
"@
|
|
65
|
-
"@nuxt/
|
|
66
|
-
"
|
|
67
|
-
"
|
|
68
|
-
"@nuxt/scripts": "^1.3.3",
|
|
69
|
-
"@nuxtjs/color-mode": "^4.0.1",
|
|
70
|
-
"@nuxtjs/i18n": "^10.6.0",
|
|
71
|
-
"@nuxtjs/mcp-toolkit": "0.18.1",
|
|
72
|
-
"@nuxtjs/robots": "^6.1.4",
|
|
73
|
-
"@nuxtjs/sitemap": "^8.3.4",
|
|
74
|
-
"@resvg/resvg-js": "^2.6.2",
|
|
75
|
-
"@shikijs/transformers": "^4.4.3",
|
|
76
|
-
"@tailwindcss/vite": "^4.3.3",
|
|
77
|
-
"@vueuse/core": "^14.4.0",
|
|
78
|
-
"class-variance-authority": "^0.7.1",
|
|
79
|
-
"clsx": "^2.1.1",
|
|
80
|
-
"h3": "1.15.11",
|
|
81
|
-
"motion-v": "^2.3.0",
|
|
82
|
-
"nitropack": "^2.13.4",
|
|
83
|
-
"nuxt-og-image": "^6.7.8",
|
|
84
|
-
"reka-ui": "^2.10.3",
|
|
85
|
-
"satori": "^0.19.3",
|
|
86
|
-
"sharp": "^0.35.3",
|
|
87
|
-
"shiki": "^4.4.3",
|
|
88
|
-
"tailwind-merge": "^3.6.0",
|
|
89
|
-
"tailwindcss": "^4.3.3",
|
|
90
|
-
"tw-animate-css": "^1.4.0",
|
|
91
|
-
"zod": "4.4.3"
|
|
45
|
+
"@comark/vue": "0.7.0",
|
|
46
|
+
"@iconify-json/lucide": "^1.2.137",
|
|
47
|
+
"@iconify/utils": "^3.1.7",
|
|
48
|
+
"@lupinum/ginko-components": "^0.5.0",
|
|
49
|
+
"@nuxt/kit": "^4.6.0",
|
|
50
|
+
"beautiful-mermaid": "^1.1.3",
|
|
51
|
+
"katex": "^0.18.11"
|
|
92
52
|
},
|
|
93
53
|
"peerDependencies": {
|
|
94
|
-
"@lupinum/ginko
|
|
95
|
-
"
|
|
96
|
-
"
|
|
97
|
-
"
|
|
54
|
+
"@lupinum/ginko": "^0.5.0",
|
|
55
|
+
"@lupinum/ginko-content": "^0.5.0",
|
|
56
|
+
"@lupinum/ginko-i18n": "^0.5.0",
|
|
57
|
+
"nuxt": "^4.6.0 || ^5.0.0-0",
|
|
58
|
+
"vue": "^3.5.0"
|
|
98
59
|
},
|
|
99
|
-
"
|
|
100
|
-
"
|
|
60
|
+
"devDependencies": {
|
|
61
|
+
"@lupinum/ginko": "0.5.0",
|
|
62
|
+
"@lupinum/ginko-content": "0.5.0",
|
|
63
|
+
"@lupinum/ginko-i18n": "0.5.0",
|
|
64
|
+
"nuxt": "4.6.0",
|
|
65
|
+
"unbuild": "3.6.1",
|
|
66
|
+
"vue": "3.5.43"
|
|
101
67
|
},
|
|
102
|
-
"scripts": {
|
|
68
|
+
"scripts": {
|
|
69
|
+
"build": "unbuild"
|
|
70
|
+
}
|
|
103
71
|
}
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
Copyright 2016 The Inter Project Authors (https://github.com/rsms/inter) Inter-Italic[opsz,wght].ttf: Copyright 2016 The Inter Project Authors (https://github.com/rsms/inter)
|
|
2
|
+
|
|
3
|
+
This Font Software is licensed under the SIL Open Font License, Version 1.1.
|
|
4
|
+
This license is copied below, and is also available with a FAQ at:
|
|
5
|
+
http://scripts.sil.org/OFL
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
-----------------------------------------------------------
|
|
9
|
+
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
|
|
10
|
+
-----------------------------------------------------------
|
|
11
|
+
|
|
12
|
+
PREAMBLE
|
|
13
|
+
The goals of the Open Font License (OFL) are to stimulate worldwide
|
|
14
|
+
development of collaborative font projects, to support the font creation
|
|
15
|
+
efforts of academic and linguistic communities, and to provide a free and
|
|
16
|
+
open framework in which fonts may be shared and improved in partnership
|
|
17
|
+
with others.
|
|
18
|
+
|
|
19
|
+
The OFL allows the licensed fonts to be used, studied, modified and
|
|
20
|
+
redistributed freely as long as they are not sold by themselves. The
|
|
21
|
+
fonts, including any derivative works, can be bundled, embedded,
|
|
22
|
+
redistributed and/or sold with any software provided that any reserved
|
|
23
|
+
names are not used by derivative works. The fonts and derivatives,
|
|
24
|
+
however, cannot be released under any other type of license. The
|
|
25
|
+
requirement for fonts to remain under this license does not apply
|
|
26
|
+
to any document created using the fonts or their derivatives.
|
|
27
|
+
|
|
28
|
+
DEFINITIONS
|
|
29
|
+
"Font Software" refers to the set of files released by the Copyright
|
|
30
|
+
Holder(s) under this license and clearly marked as such. This may
|
|
31
|
+
include source files, build scripts and documentation.
|
|
32
|
+
|
|
33
|
+
"Reserved Font Name" refers to any names specified as such after the
|
|
34
|
+
copyright statement(s).
|
|
35
|
+
|
|
36
|
+
"Original Version" refers to the collection of Font Software components as
|
|
37
|
+
distributed by the Copyright Holder(s).
|
|
38
|
+
|
|
39
|
+
"Modified Version" refers to any derivative made by adding to, deleting,
|
|
40
|
+
or substituting -- in part or in whole -- any of the components of the
|
|
41
|
+
Original Version, by changing formats or by porting the Font Software to a
|
|
42
|
+
new environment.
|
|
43
|
+
|
|
44
|
+
"Author" refers to any designer, engineer, programmer, technical
|
|
45
|
+
writer or other person who contributed to the Font Software.
|
|
46
|
+
|
|
47
|
+
PERMISSION & CONDITIONS
|
|
48
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
|
49
|
+
a copy of the Font Software, to use, study, copy, merge, embed, modify,
|
|
50
|
+
redistribute, and sell modified and unmodified copies of the Font
|
|
51
|
+
Software, subject to the following conditions:
|
|
52
|
+
|
|
53
|
+
1) Neither the Font Software nor any of its individual components,
|
|
54
|
+
in Original or Modified Versions, may be sold by itself.
|
|
55
|
+
|
|
56
|
+
2) Original or Modified Versions of the Font Software may be bundled,
|
|
57
|
+
redistributed and/or sold with any software, provided that each copy
|
|
58
|
+
contains the above copyright notice and this license. These can be
|
|
59
|
+
included either as stand-alone text files, human-readable headers or
|
|
60
|
+
in the appropriate machine-readable metadata fields within text or
|
|
61
|
+
binary files as long as those fields can be easily viewed by the user.
|
|
62
|
+
|
|
63
|
+
3) No Modified Version of the Font Software may use the Reserved Font
|
|
64
|
+
Name(s) unless explicit written permission is granted by the corresponding
|
|
65
|
+
Copyright Holder. This restriction only applies to the primary font name as
|
|
66
|
+
presented to the users.
|
|
67
|
+
|
|
68
|
+
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
|
|
69
|
+
Software shall not be used to promote, endorse or advertise any
|
|
70
|
+
Modified Version, except to acknowledge the contribution(s) of the
|
|
71
|
+
Copyright Holder(s) and the Author(s) or with their explicit written
|
|
72
|
+
permission.
|
|
73
|
+
|
|
74
|
+
5) The Font Software, modified or unmodified, in part or in whole,
|
|
75
|
+
must be distributed entirely under this license, and must not be
|
|
76
|
+
distributed under any other license. The requirement for fonts to
|
|
77
|
+
remain under this license does not apply to any document created
|
|
78
|
+
using the Font Software.
|
|
79
|
+
|
|
80
|
+
TERMINATION
|
|
81
|
+
This license becomes null and void if any of the above conditions are
|
|
82
|
+
not met.
|
|
83
|
+
|
|
84
|
+
DISCLAIMER
|
|
85
|
+
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
86
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
|
87
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
|
88
|
+
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
|
|
89
|
+
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
|
90
|
+
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
|
91
|
+
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
92
|
+
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
|
|
93
|
+
OTHER DEALINGS IN THE FONT SOFTWARE.
|
|
94
|
+
|
|
95
|
+
---- JetBrains Mono ----
|
|
96
|
+
Copyright 2020 The JetBrains Mono Project Authors (https://github.com/JetBrains/JetBrainsMono) JetBrainsMono-Italic[wght].ttf: Copyright 2020 The JetBrains Mono Project Authors (https://github.com/JetBrains/JetBrainsMono)
|
|
97
|
+
|
|
98
|
+
This Font Software is licensed under the SIL Open Font License, Version 1.1.
|
|
99
|
+
This license is copied below, and is also available with a FAQ at:
|
|
100
|
+
http://scripts.sil.org/OFL
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
-----------------------------------------------------------
|
|
104
|
+
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
|
|
105
|
+
-----------------------------------------------------------
|
|
106
|
+
|
|
107
|
+
PREAMBLE
|
|
108
|
+
The goals of the Open Font License (OFL) are to stimulate worldwide
|
|
109
|
+
development of collaborative font projects, to support the font creation
|
|
110
|
+
efforts of academic and linguistic communities, and to provide a free and
|
|
111
|
+
open framework in which fonts may be shared and improved in partnership
|
|
112
|
+
with others.
|
|
113
|
+
|
|
114
|
+
The OFL allows the licensed fonts to be used, studied, modified and
|
|
115
|
+
redistributed freely as long as they are not sold by themselves. The
|
|
116
|
+
fonts, including any derivative works, can be bundled, embedded,
|
|
117
|
+
redistributed and/or sold with any software provided that any reserved
|
|
118
|
+
names are not used by derivative works. The fonts and derivatives,
|
|
119
|
+
however, cannot be released under any other type of license. The
|
|
120
|
+
requirement for fonts to remain under this license does not apply
|
|
121
|
+
to any document created using the fonts or their derivatives.
|
|
122
|
+
|
|
123
|
+
DEFINITIONS
|
|
124
|
+
"Font Software" refers to the set of files released by the Copyright
|
|
125
|
+
Holder(s) under this license and clearly marked as such. This may
|
|
126
|
+
include source files, build scripts and documentation.
|
|
127
|
+
|
|
128
|
+
"Reserved Font Name" refers to any names specified as such after the
|
|
129
|
+
copyright statement(s).
|
|
130
|
+
|
|
131
|
+
"Original Version" refers to the collection of Font Software components as
|
|
132
|
+
distributed by the Copyright Holder(s).
|
|
133
|
+
|
|
134
|
+
"Modified Version" refers to any derivative made by adding to, deleting,
|
|
135
|
+
or substituting -- in part or in whole -- any of the components of the
|
|
136
|
+
Original Version, by changing formats or by porting the Font Software to a
|
|
137
|
+
new environment.
|
|
138
|
+
|
|
139
|
+
"Author" refers to any designer, engineer, programmer, technical
|
|
140
|
+
writer or other person who contributed to the Font Software.
|
|
141
|
+
|
|
142
|
+
PERMISSION & CONDITIONS
|
|
143
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
|
144
|
+
a copy of the Font Software, to use, study, copy, merge, embed, modify,
|
|
145
|
+
redistribute, and sell modified and unmodified copies of the Font
|
|
146
|
+
Software, subject to the following conditions:
|
|
147
|
+
|
|
148
|
+
1) Neither the Font Software nor any of its individual components,
|
|
149
|
+
in Original or Modified Versions, may be sold by itself.
|
|
150
|
+
|
|
151
|
+
2) Original or Modified Versions of the Font Software may be bundled,
|
|
152
|
+
redistributed and/or sold with any software, provided that each copy
|
|
153
|
+
contains the above copyright notice and this license. These can be
|
|
154
|
+
included either as stand-alone text files, human-readable headers or
|
|
155
|
+
in the appropriate machine-readable metadata fields within text or
|
|
156
|
+
binary files as long as those fields can be easily viewed by the user.
|
|
157
|
+
|
|
158
|
+
3) No Modified Version of the Font Software may use the Reserved Font
|
|
159
|
+
Name(s) unless explicit written permission is granted by the corresponding
|
|
160
|
+
Copyright Holder. This restriction only applies to the primary font name as
|
|
161
|
+
presented to the users.
|
|
162
|
+
|
|
163
|
+
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
|
|
164
|
+
Software shall not be used to promote, endorse or advertise any
|
|
165
|
+
Modified Version, except to acknowledge the contribution(s) of the
|
|
166
|
+
Copyright Holder(s) and the Author(s) or with their explicit written
|
|
167
|
+
permission.
|
|
168
|
+
|
|
169
|
+
5) The Font Software, modified or unmodified, in part or in whole,
|
|
170
|
+
must be distributed entirely under this license, and must not be
|
|
171
|
+
distributed under any other license. The requirement for fonts to
|
|
172
|
+
remain under this license does not apply to any document created
|
|
173
|
+
using the Font Software.
|
|
174
|
+
|
|
175
|
+
TERMINATION
|
|
176
|
+
This license becomes null and void if any of the above conditions are
|
|
177
|
+
not met.
|
|
178
|
+
|
|
179
|
+
DISCLAIMER
|
|
180
|
+
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
181
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
|
182
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
|
183
|
+
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
|
|
184
|
+
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
|
185
|
+
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
|
186
|
+
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
187
|
+
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
|
|
188
|
+
OTHER DEALINGS IN THE FONT SOFTWARE.
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
// The feed of another language: /<locale>/blog/rss.xml. The main language has none here (404), its feed has no prefix.
|
|
2
|
+
// Registered by modules/pages.ts only when the site has `posts`.
|
|
3
|
+
import { createError, defineEventHandler, getRouterParam, useRuntimeConfig } from 'nuxt/server'
|
|
4
|
+
import { serveBlogFeed } from '../lib/blogFeed'
|
|
5
|
+
export default defineEventHandler((event) => {
|
|
6
|
+
const locale = getRouterParam(event, 'locale') ?? ''
|
|
7
|
+
if (locale === (useRuntimeConfig().public.ginko as { mainLocale: string }).mainLocale) throw createError({ statusCode: 404, statusMessage: 'Not found' })
|
|
8
|
+
return serveBlogFeed(event, locale)
|
|
9
|
+
})
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
// The feed of the main language: /blog/rss.xml. Registered by modules/pages.ts only when the site has `posts`.
|
|
2
|
+
import { defineEventHandler, useRuntimeConfig } from 'nuxt/server'
|
|
3
|
+
import { serveBlogFeed } from '../lib/blogFeed'
|
|
4
|
+
export default defineEventHandler((event) => serveBlogFeed(event, (useRuntimeConfig().public.ginko as { mainLocale: string }).mainLocale))
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
// The blog feed of one language, read from the build-time file bundle (what /__ginko/files/<locale>.json serves).
|
|
2
|
+
// Only file blogs have a feed: posts that live on the platform are not in that bundle.
|
|
3
|
+
import { createError, useRuntimeConfig, type RequestEvent } from 'nuxt/server'
|
|
4
|
+
import schema from '#ginko/schema'
|
|
5
|
+
import fileBundles from '#ginko/files'
|
|
6
|
+
import locales from '#ginko/locales'
|
|
7
|
+
import type { Brick } from '@lupinum/ginko'
|
|
8
|
+
import { isVisible, todayIn } from '@lupinum/ginko/internal'
|
|
9
|
+
import { rssXml } from '@lupinum/ginko/basics'
|
|
10
|
+
|
|
11
|
+
export function serveBlogFeed(event: RequestEvent, locale: string) {
|
|
12
|
+
const config = useRuntimeConfig().public.ginko as { name: string; url: string; locales: string[]; timeZone: string; seo: { description?: string } }
|
|
13
|
+
const bundle = config.locales.includes(locale) ? fileBundles[locale as keyof typeof fileBundles] : undefined
|
|
14
|
+
const posts = bundle?.bricks.posts
|
|
15
|
+
const def = schema.bricks.posts as Brick | undefined
|
|
16
|
+
if (!bundle || posts?.kind !== 'collection' || def?.kind !== 'collection') throw createError({ statusCode: 404, statusMessage: 'No blog' })
|
|
17
|
+
|
|
18
|
+
const base = config.url.replace(/\/$/, '')
|
|
19
|
+
const authors = bundle.bricks.authors?.kind === 'collection' ? bundle.bricks.authors.items : []
|
|
20
|
+
const today = todayIn(config.timeZone)
|
|
21
|
+
const route = def.route?.[locale as keyof typeof def.route]
|
|
22
|
+
if (!route) throw createError({ statusCode: 404, statusMessage: 'No blog' })
|
|
23
|
+
const items = posts.items.filter((p) => p.urls?.[bundle.locale] && isVisible(def, p.data, today)).map((p) => ({
|
|
24
|
+
title: String(p.data.title),
|
|
25
|
+
link: base + p.urls![bundle.locale],
|
|
26
|
+
description: typeof p.data.description === 'string' ? p.data.description : undefined,
|
|
27
|
+
date: String(p.data.date),
|
|
28
|
+
authors: ((p.data.authors as string[] | undefined) ?? []).flatMap((id) => authors.find((a) => a.id === id)?.data.name ?? []).map(String),
|
|
29
|
+
}))
|
|
30
|
+
const page = base + route.replace('/[slug]', '')
|
|
31
|
+
// RSS <language> is the BCP 47 tag of the locale (`languages` in the config), like the page head.
|
|
32
|
+
const language = locales.locales.find((l) => l.code === locale)?.language ?? locale
|
|
33
|
+
event.res.headers.set('content-type', 'application/rss+xml; charset=utf-8')
|
|
34
|
+
return rssXml({ title: `${config.name} Blog`, link: page, self: `${page}/rss.xml`, description: config.seo.description ?? `${config.name} Blog`, language, items })
|
|
35
|
+
}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
// Read-only MCP server for the docs, minimal MCP Streamable HTTP: one JSON-RPC request in, one JSON response out.
|
|
2
|
+
// Methods: initialize, ping, tools/list, tools/call (search_docs, read_doc). No sessions, no streaming, no auth:
|
|
3
|
+
// it only returns what the public site already shows. Pure (the data comes in through `deps`), so tests call it directly.
|
|
4
|
+
|
|
5
|
+
export interface DocsMcpHit { title: string, heading?: string, url: string, excerpt: string }
|
|
6
|
+
export interface DocsMcpDeps {
|
|
7
|
+
search: (query: string, locale?: string) => DocsMcpHit[] | Promise<DocsMcpHit[]>
|
|
8
|
+
/** Markdown of the page at `path`, or undefined when there is none. */
|
|
9
|
+
read: (path: string) => string | undefined | Promise<string | undefined>
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const PROTOCOL = '2025-03-26'
|
|
13
|
+
const TOOLS = [
|
|
14
|
+
{
|
|
15
|
+
name: 'search_docs',
|
|
16
|
+
description: 'Search the documentation. Returns the best matching pages and sections with their path and an excerpt.',
|
|
17
|
+
inputSchema: { type: 'object', properties: { query: { type: 'string' }, locale: { type: 'string', description: 'Language code, e.g. "en". Default: the main language.' } }, required: ['query'] },
|
|
18
|
+
annotations: { readOnlyHint: true },
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
name: 'read_doc',
|
|
22
|
+
description: 'Read one documentation page as Markdown. `path` is the page path from search_docs, e.g. "/en/docs/languages".',
|
|
23
|
+
inputSchema: { type: 'object', properties: { path: { type: 'string' } }, required: ['path'] },
|
|
24
|
+
annotations: { readOnlyHint: true },
|
|
25
|
+
},
|
|
26
|
+
]
|
|
27
|
+
|
|
28
|
+
type Json = Record<string, unknown>
|
|
29
|
+
const ok = (id: unknown, result: unknown) => ({ jsonrpc: '2.0', id, result })
|
|
30
|
+
const fail = (id: unknown, code: number, message: string) => ({ jsonrpc: '2.0', id: id ?? null, error: { code, message } })
|
|
31
|
+
const text = (value: string, isError = false) => ({ content: [{ type: 'text', text: value }], ...(isError ? { isError: true } : {}) })
|
|
32
|
+
|
|
33
|
+
async function callTool(params: Json, deps: DocsMcpDeps) {
|
|
34
|
+
const args = (params.arguments ?? {}) as Json
|
|
35
|
+
if (params.name === 'search_docs') {
|
|
36
|
+
if (typeof args.query !== 'string' || !args.query.trim()) return text('`query` must be a non-empty string.', true)
|
|
37
|
+
const hits = await deps.search(args.query, typeof args.locale === 'string' ? args.locale : undefined)
|
|
38
|
+
return text(hits.length
|
|
39
|
+
? hits.map((h) => `- ${h.heading ? `${h.title} > ${h.heading}` : h.title}: ${h.url}${h.excerpt ? `\n ${h.excerpt}` : ''}`).join('\n')
|
|
40
|
+
: 'No results.')
|
|
41
|
+
}
|
|
42
|
+
if (params.name === 'read_doc') {
|
|
43
|
+
// A page path of this site only: starts with "/", no scheme-relative "//", no "..", and no query or fragment,
|
|
44
|
+
// which would reach other routes (`/__ginko/search/en.json?`) through the `.md` lookup.
|
|
45
|
+
if (typeof args.path !== 'string' || !/^\/(?!\/)[^?#]*$/.test(args.path) || args.path.split('/').includes('..')) return text('`path` must be a page path like "/en/docs/languages".', true)
|
|
46
|
+
const markdown = await deps.read(args.path.replace(/\.md$/, ''))
|
|
47
|
+
return markdown === undefined ? text(`No page at ${args.path}.`, true) : text(markdown)
|
|
48
|
+
}
|
|
49
|
+
return undefined
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** One JSON-RPC message. `undefined` means "no response" (a notification). */
|
|
53
|
+
export async function handleMcpMessage(message: unknown, deps: DocsMcpDeps): Promise<Json | undefined> {
|
|
54
|
+
if (typeof message !== 'object' || message === null || Array.isArray(message)) return fail(null, -32600, 'Invalid request')
|
|
55
|
+
const { id, method, params } = message as { id?: unknown, method?: unknown, params?: Json }
|
|
56
|
+
if (typeof method !== 'string') return fail(id, -32600, 'Invalid request')
|
|
57
|
+
if (id === undefined) return undefined // notifications such as notifications/initialized
|
|
58
|
+
switch (method) {
|
|
59
|
+
case 'initialize':
|
|
60
|
+
return ok(id, { protocolVersion: PROTOCOL, capabilities: { tools: {} }, serverInfo: { name: 'ginko-docs', version: '1' } })
|
|
61
|
+
case 'ping':
|
|
62
|
+
return ok(id, {})
|
|
63
|
+
case 'tools/list':
|
|
64
|
+
return ok(id, { tools: TOOLS })
|
|
65
|
+
case 'tools/call': {
|
|
66
|
+
const result = await callTool(params ?? {}, deps)
|
|
67
|
+
return result === undefined ? fail(id, -32602, `Unknown tool: ${String(params?.name)}`) : ok(id, result)
|
|
68
|
+
}
|
|
69
|
+
default:
|
|
70
|
+
return fail(id, -32601, `Method not found: ${method}`)
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** Messages in one batch; a client needs a handful, more only multiplies the work of one request. */
|
|
75
|
+
export const MAX_BATCH = 20
|
|
76
|
+
|
|
77
|
+
/** A request body: one message, or a batch of up to MAX_BATCH. */
|
|
78
|
+
export async function handleMcp(body: unknown, deps: DocsMcpDeps): Promise<Json | Json[] | undefined> {
|
|
79
|
+
if (!Array.isArray(body)) return handleMcpMessage(body, deps)
|
|
80
|
+
if (body.length === 0 || body.length > MAX_BATCH) return fail(null, -32600, `Invalid request: a batch holds 1 to ${MAX_BATCH} messages`)
|
|
81
|
+
const out = (await Promise.all(body.map((m) => handleMcpMessage(m, deps)))).filter((r): r is Json => r !== undefined)
|
|
82
|
+
return out.length ? out : undefined
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/** Largest request body the endpoint reads (1 MB); a JSON-RPC call is a few hundred bytes. */
|
|
86
|
+
export const MAX_BODY_BYTES = 1_000_000
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* The body text of `request`, or undefined when it is over `max` bytes. A `content-length` over the limit is
|
|
90
|
+
* refused without reading anything; without one (chunked upload) the stream is cut as soon as it passes the limit.
|
|
91
|
+
*/
|
|
92
|
+
export async function readLimitedText(request: Request, max = MAX_BODY_BYTES): Promise<string | undefined> {
|
|
93
|
+
const declared = Number(request.headers.get('content-length'))
|
|
94
|
+
if (Number.isFinite(declared) && declared > max) return undefined
|
|
95
|
+
if (!request.body) return ''
|
|
96
|
+
const reader = request.body.getReader()
|
|
97
|
+
const decoder = new TextDecoder()
|
|
98
|
+
let size = 0
|
|
99
|
+
let result = ''
|
|
100
|
+
for (;;) {
|
|
101
|
+
const { done, value } = await reader.read()
|
|
102
|
+
if (done) break
|
|
103
|
+
size += value.byteLength
|
|
104
|
+
if (size > max) { await reader.cancel(); return undefined }
|
|
105
|
+
result += decoder.decode(value, { stream: true })
|
|
106
|
+
}
|
|
107
|
+
return result + decoder.decode()
|
|
108
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
// `POST /__ginko/docs-mcp`: read-only MCP endpoint of the docs (see ../../mcp/handler.ts). It lives under `/__ginko/`
|
|
2
|
+
// like the other generated endpoints, so it cannot collide with a page or with the platform's own `/mcp`.
|
|
3
|
+
import { createError, defineEventHandler, serverFetch, setResponseStatus } from 'nuxt/server'
|
|
4
|
+
import indexes from '#ginko/search'
|
|
5
|
+
import locales from '#ginko/locales'
|
|
6
|
+
import { loadIndex, search, type SearchIndex } from '@lupinum/ginko/search'
|
|
7
|
+
import { handleMcp, readLimitedText } from '../../mcp/handler'
|
|
8
|
+
|
|
9
|
+
// The loading index per locale, kept as its promise so two first requests share one load.
|
|
10
|
+
const loaded = new Map<string, Promise<SearchIndex>>()
|
|
11
|
+
|
|
12
|
+
export default defineEventHandler(async (event) => {
|
|
13
|
+
if (event.req.method !== 'POST') throw createError({ statusCode: 405, statusMessage: 'POST only' })
|
|
14
|
+
// Over 1 MB: refuse before parsing (a plain Node host has no body limit of its own).
|
|
15
|
+
const raw = await readLimitedText(event.req)
|
|
16
|
+
if (raw === undefined) throw createError({ statusCode: 413, statusMessage: 'Request body too large (limit 1 MB)' })
|
|
17
|
+
let body: unknown
|
|
18
|
+
try { body = raw === '' ? undefined : JSON.parse(raw) } catch { throw createError({ statusCode: 400, statusMessage: 'Body must be JSON' }) }
|
|
19
|
+
const response = await handleMcp(body, {
|
|
20
|
+
async search(query, locale = locales.defaultLocale) {
|
|
21
|
+
const json = Object.hasOwn(indexes, locale) ? indexes[locale as keyof typeof indexes] : undefined
|
|
22
|
+
if (!json) return []
|
|
23
|
+
let index = loaded.get(locale)
|
|
24
|
+
if (!index) loaded.set(locale, index = loadIndex(json))
|
|
25
|
+
return search(await index, query, { limit: 8 })
|
|
26
|
+
},
|
|
27
|
+
// The same Markdown the public `<page>.md` twin returns: ask the site's own middleware, in-process (Nuxt 4.6 and 5).
|
|
28
|
+
async read(path) {
|
|
29
|
+
const response = await serverFetch(event, `${path === '/' ? '/index' : path.replace(/\/+$/, '')}.md`, { forwardHeaders: false }).catch(() => undefined)
|
|
30
|
+
return response?.ok ? response.text() : undefined
|
|
31
|
+
},
|
|
32
|
+
})
|
|
33
|
+
if (response === undefined) { setResponseStatus(event, 202); return '' }
|
|
34
|
+
return response
|
|
35
|
+
})
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
// Which pages and feeds the layer adds for a site, decided from the site's Ginko config and the paths its own pages
|
|
2
|
+
// already take. Pure, so modules/pages.ts only wires the result into Nuxt and the test can check every case.
|
|
3
|
+
export interface SiteOptions { locales?: readonly string[], pages?: Record<string, Record<string, string> | undefined>, bricks?: Record<string, { route?: Record<string, string> } | undefined> }
|
|
4
|
+
|
|
5
|
+
export interface LayerPage { name: string, path: string, /** In `app/docs-pages/`. */ file: string }
|
|
6
|
+
|
|
7
|
+
export interface LayerRoutes {
|
|
8
|
+
docs: { base: string }
|
|
9
|
+
/** Where the bricks put the blog list and the changelog (main language), whoever serves them. The app links here. */
|
|
10
|
+
addresses: { blog?: string, changelog?: string }
|
|
11
|
+
/** The pages the layer adds: each only where the site has no page of its own. */
|
|
12
|
+
pages: LayerPage[]
|
|
13
|
+
/** The blog and changelog the layer serves itself. Feeds are `<blog>/rss.xml` and `/:locale<blog>/rss.xml`. */
|
|
14
|
+
blog?: string
|
|
15
|
+
changelog?: string
|
|
16
|
+
/** A brick is configured but has no address to put a page at (the changelog brick without a route or `pages.changelog`). */
|
|
17
|
+
noAddress: string[]
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** The brick's address in the main language, with `[slug]` as `:slug()`; the list page is the parent path. */
|
|
21
|
+
function listPath(route: string | undefined): string | undefined {
|
|
22
|
+
if (!route) return undefined
|
|
23
|
+
const list = route.replace(/\/\[slug\]$/, '')
|
|
24
|
+
return list.includes('[') ? undefined : list || '/'
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** A page as Nuxt has it once every `pages:extend` ran: its scanned path and, from the templates' page-paths module, its addresses per language. */
|
|
28
|
+
export interface PageLike { path: string, meta?: { i18n?: false | { paths?: Record<string, string> } } | undefined }
|
|
29
|
+
|
|
30
|
+
/** The addresses a page answers at in the main language: its path and `meta.i18n.paths[main]` when set (`/news` answers at `/blog`). */
|
|
31
|
+
function addressesOf(page: PageLike, main: string): string[] {
|
|
32
|
+
const mapped = page.meta?.i18n && page.meta.i18n.paths?.[main]
|
|
33
|
+
return mapped ? [page.path, mapped] : [page.path]
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function layerRoutes(site: SiteOptions | undefined, pages: Iterable<PageLike>): LayerRoutes {
|
|
37
|
+
const bricks = site?.bricks ?? {}
|
|
38
|
+
const main = site?.locales?.[0] ?? ''
|
|
39
|
+
const taken = new Set<string>()
|
|
40
|
+
for (const page of pages) for (const path of addressesOf(page, main)) taken.add(path)
|
|
41
|
+
// docsCollection writes `<base>/[slug]` for the main language; other languages get a prefix from ginko-i18n.
|
|
42
|
+
const docsBase = bricks.docs?.route?.[main]?.replace(/\/\[slug\]$/, '') ?? '/docs'
|
|
43
|
+
const blog = bricks.posts ? listPath(bricks.posts.route?.[main]) : undefined
|
|
44
|
+
// The changelog has no page of its own per entry: its list lives at the route the brick has, else at `pages.changelog`.
|
|
45
|
+
const changelog = bricks.changelog ? listPath(bricks.changelog.route?.[main]) ?? site?.pages?.changelog?.[main] : undefined
|
|
46
|
+
const free = (path: string | undefined) => (path && !taken.has(path) ? path : undefined)
|
|
47
|
+
const noAddress = [...(bricks.posts && !blog ? ['posts'] : []), ...(bricks.changelog && !changelog ? ['changelog'] : [])]
|
|
48
|
+
const addedBlog = free(blog)
|
|
49
|
+
const addedChangelog = free(changelog)
|
|
50
|
+
const wanted: Array<LayerPage | undefined> = [
|
|
51
|
+
{ name: 'docs-slug', path: `${docsBase}/:slug()`, file: 'doc.vue' },
|
|
52
|
+
{ name: 'docs', path: docsBase, file: 'docs.vue' },
|
|
53
|
+
{ name: 'index', path: '/', file: 'index.vue' },
|
|
54
|
+
addedBlog ? { name: 'blog', path: addedBlog, file: 'blog.vue' } : undefined,
|
|
55
|
+
addedBlog ? { name: 'blog-slug', path: `${addedBlog}/:slug()`, file: 'post.vue' } : undefined,
|
|
56
|
+
addedChangelog ? { name: 'changelog', path: addedChangelog, file: 'changelog.vue' } : undefined,
|
|
57
|
+
]
|
|
58
|
+
return {
|
|
59
|
+
docs: { base: docsBase },
|
|
60
|
+
addresses: { ...(blog && { blog }), ...(changelog && { changelog }) },
|
|
61
|
+
pages: wanted.filter((page): page is LayerPage => !!page && !taken.has(page.path)),
|
|
62
|
+
...(addedBlog && { blog: addedBlog }),
|
|
63
|
+
...(addedChangelog && { changelog: addedChangelog }),
|
|
64
|
+
noAddress,
|
|
65
|
+
}
|
|
66
|
+
}
|
package/shared/paths.ts
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
// The layer's fixed addresses, shared by the bricks (src/config.ts), the page module and the app. Internal: not
|
|
2
|
+
// exported from `@lupinum/ginko-docs/config`, so the paths can gain a `base` option later without a breaking change.
|
|
3
|
+
export const BLOG_PATH = '/blog'
|
|
4
|
+
export const CHANGELOG_PATH = '/changelog'
|