@lupinum/ginko-docs 0.4.0-rc.8 → 0.4.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.
Files changed (296) hide show
  1. package/CHANGELOG.md +78 -0
  2. package/LICENSE +1 -1
  3. package/README.md +206 -54
  4. package/app/app.config.ts +42 -55
  5. package/app/assets/docs.css +555 -0
  6. package/app/components/DocsBanner.vue +29 -0
  7. package/app/components/DocsExample.vue +43 -0
  8. package/app/components/DocsFooter.vue +19 -0
  9. package/app/components/DocsHeader.vue +112 -0
  10. package/app/components/DocsLanding.vue +55 -0
  11. package/app/components/DocsLanguage.vue +29 -0
  12. package/app/components/DocsNavTree.vue +38 -0
  13. package/app/components/DocsPage.vue +84 -0
  14. package/app/components/DocsPageActions.vue +47 -0
  15. package/app/components/DocsPost.vue +58 -0
  16. package/app/components/DocsSearch.vue +134 -0
  17. package/app/components/DocsShell.vue +20 -0
  18. package/app/components/DocsSidebar.vue +18 -0
  19. package/app/components/DocsTable.vue +11 -0
  20. package/app/components/DocsThemeToggle.vue +45 -0
  21. package/app/components/DocsToc.vue +73 -0
  22. package/app/composables/useDocs.ts +52 -0
  23. package/app/composables/useDocsBlog.ts +32 -0
  24. package/app/composables/useDocsCopy.ts +17 -0
  25. package/app/composables/useDocsPopover.ts +36 -0
  26. package/app/composables/useDocsSite.ts +10 -0
  27. package/app/docs-pages/blog.vue +47 -0
  28. package/app/docs-pages/changelog.vue +27 -0
  29. package/app/docs-pages/doc.vue +14 -0
  30. package/app/docs-pages/docs.vue +9 -0
  31. package/app/docs-pages/index.vue +12 -0
  32. package/app/docs-pages/post.vue +8 -0
  33. package/app/error.vue +23 -115
  34. package/app/layouts/default.vue +25 -7
  35. package/app/lib/blogTypes.ts +8 -0
  36. package/app/lib/docsFirst.ts +11 -0
  37. package/app/lib/docsInit.ts +18 -0
  38. package/app/lib/docsNav.ts +36 -0
  39. package/app/lib/docsPage.ts +44 -0
  40. package/app/lib/markdownComponents.ts +16 -0
  41. package/app/lib/previewToc.ts +18 -0
  42. package/dist/config.d.mts +232 -0
  43. package/dist/config.d.ts +232 -0
  44. package/dist/config.mjs +114 -0
  45. package/dist/pages.d.mts +5 -0
  46. package/dist/pages.d.ts +5 -0
  47. package/dist/pages.mjs +36 -0
  48. package/dist/shared/ginko-docs.BwbF7JBR.mjs +4 -0
  49. package/i18n/de.json +1 -0
  50. package/i18n/en.json +1 -0
  51. package/icons/README.md +2 -0
  52. package/icons/bluesky.svg +1 -0
  53. package/icons/claude.svg +1 -0
  54. package/icons/discord.svg +1 -0
  55. package/icons/github.svg +1 -0
  56. package/icons/linkedin.svg +1 -0
  57. package/icons/mastodon.svg +1 -0
  58. package/icons/openai.svg +1 -0
  59. package/icons/x.svg +1 -0
  60. package/icons/youtube.svg +1 -0
  61. package/nuxt.config.ts +34 -209
  62. package/package.json +43 -76
  63. package/public/_docs/fonts/LICENSE +188 -0
  64. package/public/_docs/fonts/inter-latin-wght-normal.woff2 +0 -0
  65. package/public/_docs/fonts/jetbrains-mono-latin-400-normal.woff2 +0 -0
  66. package/public/_docs/fonts/jetbrains-mono-latin-500-normal.woff2 +0 -0
  67. package/server/feeds/blog-locale.get.ts +9 -0
  68. package/server/feeds/blog.get.ts +4 -0
  69. package/server/lib/blogFeed.ts +35 -0
  70. package/server/lib/rss.ts +57 -0
  71. package/server/mcp/handler.ts +108 -0
  72. package/server/routes/__ginko/docs-mcp.ts +35 -0
  73. package/shared/paths.ts +4 -0
  74. package/app/app.vue +0 -98
  75. package/app/assets/css/prose.css +0 -3240
  76. package/app/assets/css/tailwind.css +0 -394
  77. package/app/assets/css/theme-palettes.css +0 -309
  78. package/app/assets/css/theme-presets.css +0 -141
  79. package/app/components/OgImage/GinkoDocs.satori.vue +0 -238
  80. package/app/components/content/DocumentPageShell.vue +0 -55
  81. package/app/components/content/Feedback.vue +0 -102
  82. package/app/components/content/ImageZoomDialog.vue +0 -84
  83. package/app/components/content/PageMarkdownCopy.vue +0 -104
  84. package/app/components/content/imageZoom.ts +0 -25
  85. package/app/components/mdc/MdcAccordion.vue +0 -67
  86. package/app/components/mdc/MdcAccordionItem.vue +0 -42
  87. package/app/components/mdc/MdcApi.vue +0 -159
  88. package/app/components/mdc/MdcAside.vue +0 -23
  89. package/app/components/mdc/MdcCallout.vue +0 -61
  90. package/app/components/mdc/MdcCard.vue +0 -175
  91. package/app/components/mdc/MdcCards.vue +0 -30
  92. package/app/components/mdc/MdcCenter.vue +0 -71
  93. package/app/components/mdc/MdcCodeGroup.vue +0 -197
  94. package/app/components/mdc/MdcCodeTree.vue +0 -234
  95. package/app/components/mdc/MdcCollapse.vue +0 -61
  96. package/app/components/mdc/MdcColumn.vue +0 -70
  97. package/app/components/mdc/MdcDropcap.vue +0 -84
  98. package/app/components/mdc/MdcError.vue +0 -23
  99. package/app/components/mdc/MdcExcerpt.vue +0 -26
  100. package/app/components/mdc/MdcFigure.vue +0 -85
  101. package/app/components/mdc/MdcFiles.vue +0 -120
  102. package/app/components/mdc/MdcIdea.vue +0 -17
  103. package/app/components/mdc/MdcInfo.vue +0 -17
  104. package/app/components/mdc/MdcInlineToc.vue +0 -51
  105. package/app/components/mdc/MdcKbd.vue +0 -10
  106. package/app/components/mdc/MdcLayout.vue +0 -57
  107. package/app/components/mdc/MdcNote.vue +0 -17
  108. package/app/components/mdc/MdcQuiz.vue +0 -241
  109. package/app/components/mdc/MdcQuizQuestion.vue +0 -221
  110. package/app/components/mdc/MdcReadMore.vue +0 -70
  111. package/app/components/mdc/MdcSteps.vue +0 -59
  112. package/app/components/mdc/MdcSuccess.vue +0 -23
  113. package/app/components/mdc/MdcTab.vue +0 -17
  114. package/app/components/mdc/MdcTabs.vue +0 -150
  115. package/app/components/mdc/MdcTimeline.vue +0 -17
  116. package/app/components/mdc/MdcTimelineItem.vue +0 -40
  117. package/app/components/mdc/MdcWarning.vue +0 -23
  118. package/app/components/mdc/api.utils.ts +0 -132
  119. package/app/components/mdc/code-tree.utils.ts +0 -22
  120. package/app/components/mdc/icons.ts +0 -10
  121. package/app/components/mdc/quiz.utils.ts +0 -38
  122. package/app/components/prose/ProseA.vue +0 -27
  123. package/app/components/prose/ProseBlockquote.vue +0 -30
  124. package/app/components/prose/ProseCode.vue +0 -14
  125. package/app/components/prose/ProseEm.vue +0 -14
  126. package/app/components/prose/ProseH1.vue +0 -18
  127. package/app/components/prose/ProseH2.vue +0 -29
  128. package/app/components/prose/ProseH3.vue +0 -29
  129. package/app/components/prose/ProseH4.vue +0 -29
  130. package/app/components/prose/ProseH5.vue +0 -15
  131. package/app/components/prose/ProseH6.vue +0 -15
  132. package/app/components/prose/ProseHr.vue +0 -12
  133. package/app/components/prose/ProseImg.vue +0 -55
  134. package/app/components/prose/ProseLi.vue +0 -14
  135. package/app/components/prose/ProseOl.vue +0 -14
  136. package/app/components/prose/ProseP.vue +0 -14
  137. package/app/components/prose/ProsePre.vue +0 -180
  138. package/app/components/prose/ProseStrong.vue +0 -14
  139. package/app/components/prose/ProseTable.vue +0 -18
  140. package/app/components/prose/ProseTbody.vue +0 -14
  141. package/app/components/prose/ProseTd.vue +0 -14
  142. package/app/components/prose/ProseTh.vue +0 -14
  143. package/app/components/prose/ProseThead.vue +0 -14
  144. package/app/components/prose/ProseTr.vue +0 -14
  145. package/app/components/prose/ProseUl.vue +0 -14
  146. package/app/components/site/ModeToggle.vue +0 -81
  147. package/app/components/site/SiteBanner.vue +0 -97
  148. package/app/components/site/SiteFooter.vue +0 -90
  149. package/app/components/site/SiteHeader.vue +0 -371
  150. package/app/components/site/SiteHeroCode.vue +0 -173
  151. package/app/components/site/SiteInteractionLayer.vue +0 -42
  152. package/app/components/site/SiteLocaleSwitcher.vue +0 -190
  153. package/app/components/site/SiteLogoMark.vue +0 -12
  154. package/app/components/site/SiteSkipLink.vue +0 -14
  155. package/app/components/site/SiteSocialLinks.vue +0 -33
  156. package/app/components/site/header-utils.ts +0 -3
  157. package/app/components/site/mode-toggle.utils.ts +0 -18
  158. package/app/components/ui/badge/Badge.vue +0 -28
  159. package/app/components/ui/badge/index.ts +0 -24
  160. package/app/components/ui/button/Button.vue +0 -31
  161. package/app/components/ui/button/index.ts +0 -35
  162. package/app/components/ui/collapsible/Collapsible.vue +0 -15
  163. package/app/components/ui/collapsible/CollapsibleContent.vue +0 -25
  164. package/app/components/ui/collapsible/CollapsibleTrigger.vue +0 -12
  165. package/app/components/ui/collapsible/index.ts +0 -3
  166. package/app/components/ui/dropdown-menu/DropdownMenu.vue +0 -15
  167. package/app/components/ui/dropdown-menu/DropdownMenuContent.vue +0 -50
  168. package/app/components/ui/dropdown-menu/DropdownMenuItem.vue +0 -41
  169. package/app/components/ui/dropdown-menu/DropdownMenuRadioGroup.vue +0 -15
  170. package/app/components/ui/dropdown-menu/DropdownMenuRadioItem.vue +0 -37
  171. package/app/components/ui/dropdown-menu/DropdownMenuSeparator.vue +0 -23
  172. package/app/components/ui/dropdown-menu/DropdownMenuTrigger.vue +0 -14
  173. package/app/components/ui/dropdown-menu/index.ts +0 -7
  174. package/app/components/ui/kbd/Kbd.vue +0 -23
  175. package/app/components/ui/kbd/index.ts +0 -21
  176. package/app/components/ui/scroll-area/ScrollArea.vue +0 -39
  177. package/app/components/ui/scroll-area/ScrollBar.vue +0 -33
  178. package/app/components/ui/scroll-area/index.ts +0 -2
  179. package/app/components/ui/separator/Separator.vue +0 -27
  180. package/app/components/ui/separator/index.ts +0 -1
  181. package/app/components/ui/sheet/Sheet.vue +0 -15
  182. package/app/components/ui/sheet/SheetContent.vue +0 -59
  183. package/app/components/ui/sheet/SheetDescription.vue +0 -21
  184. package/app/components/ui/sheet/SheetOverlay.vue +0 -26
  185. package/app/components/ui/sheet/SheetTitle.vue +0 -21
  186. package/app/components/ui/sheet/SheetTrigger.vue +0 -12
  187. package/app/components/ui/sheet/index.ts +0 -5
  188. package/app/components/ui/tabs/Tabs.vue +0 -24
  189. package/app/components/ui/tabs/TabsList.vue +0 -26
  190. package/app/components/ui/tabs/TabsTrigger.vue +0 -28
  191. package/app/components/ui/tabs/index.ts +0 -3
  192. package/app/composables/site-navigation.utils.ts +0 -133
  193. package/app/composables/useCanonicalUrl.ts +0 -9
  194. package/app/composables/useContentRouteAlternates.ts +0 -35
  195. package/app/composables/useGinkoAnalytics.ts +0 -52
  196. package/app/composables/useGinkoDocsConfig.ts +0 -6
  197. package/app/composables/useGinkoOgImage.ts +0 -20
  198. package/app/composables/useLocalizedPath.ts +0 -12
  199. package/app/composables/useLocalizedRouteSwitch.ts +0 -31
  200. package/app/composables/useMetaKey.ts +0 -18
  201. package/app/composables/usePageMarkdownActions.ts +0 -79
  202. package/app/composables/useProseAppearance.ts +0 -24
  203. package/app/composables/useSchemaJsonLd.ts +0 -25
  204. package/app/composables/useSiteNavigation.ts +0 -64
  205. package/app/config/site.utils.ts +0 -36
  206. package/app/features/docs/components/DocsBreadcrumb.vue +0 -37
  207. package/app/features/docs/components/DocsContributeLinks.vue +0 -75
  208. package/app/features/docs/components/DocsMobileToc.vue +0 -108
  209. package/app/features/docs/components/DocsPageContent.vue +0 -234
  210. package/app/features/docs/components/DocsPageNav.vue +0 -57
  211. package/app/features/docs/components/DocsSidebar.vue +0 -145
  212. package/app/features/docs/components/DocsSidebarDropdown.vue +0 -70
  213. package/app/features/docs/components/DocsSidebarItem.vue +0 -122
  214. package/app/features/docs/components/DocsSidebarList.vue +0 -35
  215. package/app/features/docs/components/DocsSidebarRow.vue +0 -27
  216. package/app/features/docs/components/DocsSidebarTabs.vue +0 -59
  217. package/app/features/docs/components/DocsToc.vue +0 -106
  218. package/app/features/docs/composables/useDocsNavigation.ts +0 -29
  219. package/app/features/docs/composables/useDocsNavigationData.ts +0 -18
  220. package/app/features/docs/composables/useRevealActive.ts +0 -58
  221. package/app/features/docs/composables/useScrollspy.ts +0 -135
  222. package/app/features/docs/docs-navigation.ts +0 -157
  223. package/app/features/docs/toc-context.ts +0 -6
  224. package/app/features/search/command-center.ts +0 -110
  225. package/app/features/search/components/SiteCommandCenter.vue +0 -359
  226. package/app/features/search/components/SiteSearchPageHighlight.vue +0 -126
  227. package/app/features/search/useCommandCenter.ts +0 -292
  228. package/app/layouts/blog.vue +0 -15
  229. package/app/layouts/docs.vue +0 -20
  230. package/app/lib/errors.ts +0 -37
  231. package/app/lib/schema-org.ts +0 -38
  232. package/app/pages/blog/[slug].vue +0 -228
  233. package/app/pages/blog/index.vue +0 -130
  234. package/app/pages/docs/[...slug].vue +0 -10
  235. package/app/pages/docs/index.vue +0 -19
  236. package/app/pages/index.vue +0 -278
  237. package/app/plugins/ginko-docs-theme.ts +0 -14
  238. package/app/plugins/ssr-width.ts +0 -6
  239. package/app/utils/content.ts +0 -67
  240. package/app/utils/file-icons.ts +0 -90
  241. package/app/utils/index.ts +0 -7
  242. package/app/utils/repository.ts +0 -20
  243. package/components.ts +0 -9
  244. package/content-collections.ts +0 -89
  245. package/content.js +0 -200
  246. package/content.ts +0 -104
  247. package/i18n/i18n.config.ts +0 -10
  248. package/i18n/locales.ts +0 -81
  249. package/i18n/messages/global/banner.ts +0 -8
  250. package/i18n/messages/global/blog.ts +0 -18
  251. package/i18n/messages/global/command.ts +0 -49
  252. package/i18n/messages/global/docs.ts +0 -36
  253. package/i18n/messages/global/errors.ts +0 -36
  254. package/i18n/messages/global/feedback.ts +0 -11
  255. package/i18n/messages/global/index.ts +0 -21
  256. package/i18n/messages/global/nav.ts +0 -26
  257. package/i18n/messages/global/site.ts +0 -4
  258. package/i18n/messages/global/theme.ts +0 -9
  259. package/i18n/messages/index.ts +0 -20
  260. package/i18n/messages/localize.ts +0 -35
  261. package/i18n/routes.ts +0 -33
  262. package/icon-bundle.ts +0 -116
  263. package/index.d.ts +0 -37
  264. package/modules/analytics-boundary.ts +0 -23
  265. package/modules/feature-routing.ts +0 -68
  266. package/modules/syntax-highlighting.ts +0 -120
  267. package/public/fonts/public-sans/LICENSE +0 -93
  268. package/public/fonts/public-sans/README.md +0 -12
  269. package/public/fonts/public-sans/public-sans-400-normal-latin.woff +0 -0
  270. package/public/fonts/public-sans/public-sans-500-normal-latin.woff +0 -0
  271. package/public/fonts/public-sans/public-sans-600-normal-latin.woff +0 -0
  272. package/public/fonts/public-sans/public-sans-700-normal-latin.woff +0 -0
  273. package/runtime/generate-favicons.mjs +0 -124
  274. package/runtime/server/agent-markdown.ts +0 -19
  275. package/server/api/_ginko-docs/redirects.get.ts +0 -28
  276. package/server/mcp/tools/get-page.ts +0 -14
  277. package/server/mcp/tools/list-pages.ts +0 -10
  278. package/server/middleware/redirects.ts +0 -22
  279. package/server/plugins/agent-markdown.ts +0 -6
  280. package/server/plugins/sitemap-policy.ts +0 -12
  281. package/server/routes/.well-known/ai-catalog.json.get.ts +0 -10
  282. package/server/routes/[locale]/blog/rss.xml.get.ts +0 -13
  283. package/server/routes/blog/rss.xml.get.ts +0 -5
  284. package/server/routes/mcp/server-card.get.ts +0 -14
  285. package/server/utils/blog-feed.ts +0 -51
  286. package/server/utils/feed.ts +0 -70
  287. package/server/utils/mcp-server-card.ts +0 -45
  288. package/server/utils/redirects.ts +0 -38
  289. package/server/utils/redirects.utils.ts +0 -58
  290. package/server/utils/sitemap.ts +0 -45
  291. package/shared/route-slugs.ts +0 -5
  292. package/shared/theme-palettes.ts +0 -37
  293. package/shared/theme-presets.ts +0 -3
  294. package/shared/types/app-config.ts +0 -172
  295. package/shared/types/nuxt-config.ts +0 -14
  296. package/tags.ts +0 -192
@@ -1,124 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- import { mkdir, readFile, writeFile } from "node:fs/promises";
4
- import { extname, join, resolve } from "node:path";
5
- import { pathToFileURL } from "node:url";
6
- import sharp from "sharp";
7
-
8
- const ICO_SIZES = [16, 32, 48];
9
-
10
- function createIco(images) {
11
- const headerSize = 6;
12
- const entrySize = 16;
13
- const directory = Buffer.alloc(headerSize + entrySize * images.length);
14
-
15
- directory.writeUInt16LE(0, 0);
16
- directory.writeUInt16LE(1, 2);
17
- directory.writeUInt16LE(images.length, 4);
18
-
19
- let imageOffset = directory.length;
20
- images.forEach(({ size, data }, index) => {
21
- const entryOffset = headerSize + entrySize * index;
22
- directory.writeUInt8(size === 256 ? 0 : size, entryOffset);
23
- directory.writeUInt8(size === 256 ? 0 : size, entryOffset + 1);
24
- directory.writeUInt8(0, entryOffset + 2);
25
- directory.writeUInt8(0, entryOffset + 3);
26
- directory.writeUInt16LE(1, entryOffset + 4);
27
- directory.writeUInt16LE(32, entryOffset + 6);
28
- directory.writeUInt32LE(data.length, entryOffset + 8);
29
- directory.writeUInt32LE(imageOffset, entryOffset + 12);
30
- imageOffset += data.length;
31
- });
32
-
33
- return Buffer.concat([directory, ...images.map(({ data }) => data)]);
34
- }
35
-
36
- async function renderSquare(source, size) {
37
- return sharp(source, { density: 512 }).resize(size, size, { fit: "contain" }).png().toBuffer();
38
- }
39
-
40
- function embeddedSvg(png) {
41
- return `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" role="img">
42
- <image width="512" height="512" href="data:image/png;base64,${png.toString("base64")}" />
43
- </svg>
44
- `;
45
- }
46
-
47
- export async function generateFavicons({ source, outputDirectory }) {
48
- const sourcePath = resolve(source);
49
- const outputPath = resolve(outputDirectory);
50
- const sourceBuffer = await readFile(sourcePath);
51
- const metadata = await sharp(sourceBuffer, { density: 512 }).metadata();
52
-
53
- if (!metadata.width || !metadata.height) {
54
- throw new Error(`Could not read icon dimensions from ${sourcePath}`);
55
- }
56
- if (metadata.width !== metadata.height) {
57
- throw new Error(
58
- `The favicon source must be square. Received ${metadata.width}x${metadata.height}.`,
59
- );
60
- }
61
-
62
- const [png96, appleTouchIcon, ...icoPngs] = await Promise.all([
63
- renderSquare(sourceBuffer, 96),
64
- renderSquare(sourceBuffer, 180),
65
- ...ICO_SIZES.map(async (size) => ({ size, data: await renderSquare(sourceBuffer, size) })),
66
- ]);
67
-
68
- const svg =
69
- extname(sourcePath).toLowerCase() === ".svg"
70
- ? sourceBuffer
71
- : Buffer.from(embeddedSvg(await renderSquare(sourceBuffer, 512)));
72
-
73
- await mkdir(outputPath, { recursive: true });
74
- await Promise.all([
75
- writeFile(join(outputPath, "favicon.svg"), svg),
76
- writeFile(join(outputPath, "favicon-96x96.png"), png96),
77
- writeFile(join(outputPath, "apple-touch-icon.png"), appleTouchIcon),
78
- writeFile(join(outputPath, "favicon.ico"), createIco(icoPngs)),
79
- ]);
80
-
81
- return ["favicon.svg", "favicon-96x96.png", "apple-touch-icon.png", "favicon.ico"];
82
- }
83
-
84
- function parseArguments(arguments_) {
85
- const options = {
86
- source: "public/favicon.svg",
87
- outputDirectory: "public",
88
- };
89
-
90
- for (let index = 0; index < arguments_.length; index += 1) {
91
- const argument = arguments_[index];
92
- if (argument === "--source") options.source = arguments_[++index];
93
- else if (argument === "--out") options.outputDirectory = arguments_[++index];
94
- else if (argument === "--help" || argument === "-h") return undefined;
95
- else throw new Error(`Unknown argument: ${argument}`);
96
- }
97
-
98
- if (!options.source || !options.outputDirectory) {
99
- throw new Error("Both --source and --out require a path.");
100
- }
101
- return options;
102
- }
103
-
104
- async function main() {
105
- const options = parseArguments(process.argv.slice(2));
106
- if (!options) {
107
- console.log(`Usage: ginko-docs-favicons [--source <path>] [--out <directory>]
108
-
109
- Defaults:
110
- --source public/favicon.svg
111
- --out public`);
112
- return;
113
- }
114
-
115
- const files = await generateFavicons(options);
116
- console.log(`Generated ${files.join(", ")} in ${resolve(options.outputDirectory)}`);
117
- }
118
-
119
- if (process.argv[1] && pathToFileURL(resolve(process.argv[1])).href === import.meta.url) {
120
- main().catch((error) => {
121
- console.error(error instanceof Error ? error.message : error);
122
- process.exitCode = 1;
123
- });
124
- }
@@ -1,19 +0,0 @@
1
- import {
2
- registerAgentMarkdownSerializers,
3
- type AgentMarkdownSerializer,
4
- type AgentMarkdownSerializerMap,
5
- } from "@lupinum/ginko-content/agent-registry";
6
- import { contentComponentTags } from "../../tags";
7
-
8
- const renderXmlComponent: AgentMarkdownSerializer = (node, ctx) =>
9
- ctx.xmlComponent(node.tag || "component", ctx.cleanProps(node), ctx.renderChildren(node));
10
-
11
- export function registerContentComponentAgentMarkdownSerializers() {
12
- const serializers: AgentMarkdownSerializerMap = {};
13
- for (const [tag, component] of Object.entries(contentComponentTags)) {
14
- serializers[tag] = renderXmlComponent;
15
- serializers[component] = renderXmlComponent;
16
- }
17
-
18
- registerAgentMarkdownSerializers(serializers);
19
- }
@@ -1,28 +0,0 @@
1
- import { defineEventHandler, setHeader } from "h3";
2
- import { loadRedirectMap } from "../../utils/redirects";
3
-
4
- const escapeHtml = (value: string) =>
5
- value
6
- .replaceAll("&", "&amp;")
7
- .replaceAll("<", "&lt;")
8
- .replaceAll(">", "&gt;")
9
- .replaceAll('"', "&quot;");
10
-
11
- /**
12
- * Prerender seed: returns a link page of every `redirectFrom` source so the
13
- * crawler visits each old path and materializes its redirect stub. Conflict
14
- * validation throws here, which fails the build via `failOnError`.
15
- */
16
- export default defineEventHandler(async (event) => {
17
- const redirects = await loadRedirectMap(event);
18
-
19
- if (import.meta.prerender) {
20
- setHeader(event, "content-type", "text/html; charset=utf-8");
21
- const links = Array.from(redirects.keys())
22
- .map((source) => `<a href="${escapeHtml(source)}"></a>`)
23
- .join("");
24
- return `<!doctype html><html><head><meta charset="utf-8"></head><body>${links}</body></html>`;
25
- }
26
-
27
- return { count: redirects.size, redirects: Object.fromEntries(redirects) };
28
- });
@@ -1,14 +0,0 @@
1
- import { defineMcpTool } from "@nuxtjs/mcp-toolkit/server";
2
- import { agentRawPathForRoute } from "@lupinum/ginko-content/agent-paths";
3
- import { z } from "zod";
4
-
5
- export default defineMcpTool({
6
- description: "Read a documentation page as Markdown.",
7
- annotations: { readOnlyHint: true },
8
- inputSchema: {
9
- path: z.string().startsWith("/").describe("Documentation path, for example /docs/introduction"),
10
- },
11
- handler: async ({ path }) => {
12
- return await $fetch<string>(agentRawPathForRoute(path), { responseType: "text" });
13
- },
14
- });
@@ -1,10 +0,0 @@
1
- import { defineMcpTool } from "@nuxtjs/mcp-toolkit/server";
2
-
3
- export default defineMcpTool({
4
- description: "List the documentation pages available to agents.",
5
- annotations: { readOnlyHint: true },
6
- handler: async () => {
7
- const index = await $fetch<string>("/llms.txt", { responseType: "text" });
8
- return index;
9
- },
10
- });
@@ -1,22 +0,0 @@
1
- import { defineEventHandler, sendRedirect } from "h3";
2
- import { loadRedirectMap } from "../utils/redirects";
3
- import { normalizeRedirectPath } from "../utils/redirects.utils";
4
-
5
- /**
6
- * Serves authored `redirectFrom` moves as 301s. During prerender this runs
7
- * before the catch-all page would SSR a 404 for the crawled old path, so the
8
- * static build materializes the same meta-refresh stub the docs root uses.
9
- */
10
- export default defineEventHandler(async (event) => {
11
- if (event.method !== "GET" && event.method !== "HEAD") return;
12
-
13
- const path = normalizeRedirectPath(event.path);
14
- const lastSegment = path.slice(path.lastIndexOf("/") + 1);
15
- if (path.startsWith("/api/") || path.startsWith("/_") || lastSegment.includes(".")) return;
16
-
17
- const redirects = await loadRedirectMap(event);
18
- const target = redirects.get(path);
19
- if (target) {
20
- return sendRedirect(event, target, 301);
21
- }
22
- });
@@ -1,6 +0,0 @@
1
- import { defineNitroPlugin } from "nitropack/runtime/plugin";
2
- import { registerContentComponentAgentMarkdownSerializers } from "../../runtime/server/agent-markdown";
3
-
4
- export default defineNitroPlugin(() => {
5
- registerContentComponentAgentMarkdownSerializers();
6
- });
@@ -1,12 +0,0 @@
1
- import { defineNitroPlugin } from "nitropack/runtime/plugin";
2
- import { filterSitemapEntries } from "../utils/sitemap";
3
-
4
- export default defineNitroPlugin((nitro) => {
5
- nitro.hooks.hook("sitemap:resolved", (context) => {
6
- const content = useRuntimeConfig(context.event).content;
7
- context.urls = filterSitemapEntries(context.urls, {
8
- locales: content.locales,
9
- blogEnabled: Boolean(content.collections.blog),
10
- });
11
- });
12
- });
@@ -1,10 +0,0 @@
1
- import { useAppConfig } from "#imports";
2
- import { createGinkoDocsAiCatalog } from "../../utils/mcp-server-card";
3
-
4
- export default defineEventHandler((event) => {
5
- event.node.res.setHeader("access-control-allow-origin", "*");
6
- event.node.res.setHeader("cache-control", "public, max-age=3600");
7
- event.node.res.setHeader("content-type", "application/ai-catalog+json; charset=utf-8");
8
-
9
- return createGinkoDocsAiCatalog(useAppConfig().ginkoDocs.site.url);
10
- });
@@ -1,13 +0,0 @@
1
- import { createError, defineEventHandler, getRouterParam } from "h3";
2
- import { defaultLocale, isLocaleCode } from "../../../../i18n/locales";
3
- import { serveBlogFeed } from "../../../utils/blog-feed";
4
-
5
- // A param route keeps the router free of static locale segments; a static
6
- // /de/... node would shadow ginko-content's /:locale/llms.txt routes.
7
- export default defineEventHandler(async (event) => {
8
- const locale = getRouterParam(event, "locale");
9
- if (!locale || !isLocaleCode(locale) || locale === defaultLocale) {
10
- throw createError({ statusCode: 404, statusMessage: "Page not found" });
11
- }
12
- return serveBlogFeed(event, locale);
13
- });
@@ -1,5 +0,0 @@
1
- import { defineEventHandler } from "h3";
2
- import { defaultLocale } from "../../../i18n/locales";
3
- import { serveBlogFeed } from "../../utils/blog-feed";
4
-
5
- export default defineEventHandler((event) => serveBlogFeed(event, defaultLocale));
@@ -1,14 +0,0 @@
1
- import { useAppConfig } from "#imports";
2
- import packageMetadata from "../../../package.json" with { type: "json" };
3
- import { createGinkoDocsServerCard } from "../../utils/mcp-server-card";
4
-
5
- export default defineEventHandler((event) => {
6
- event.node.res.setHeader("access-control-allow-origin", "*");
7
- event.node.res.setHeader("cache-control", "public, max-age=3600");
8
- event.node.res.setHeader("content-type", "application/mcp-server-card+json; charset=utf-8");
9
-
10
- return createGinkoDocsServerCard({
11
- origin: useAppConfig().ginkoDocs.site.url,
12
- version: packageMetadata.version,
13
- });
14
- });
@@ -1,51 +0,0 @@
1
- import type { H3Event } from "h3";
2
- import { createError, setHeader } from "h3";
3
- import { many } from "@lupinum/ginko-content/server";
4
- import { useAppConfig, useRuntimeConfig } from "#imports";
5
- import { blog } from "../../i18n/messages/global/blog";
6
- import { locales, localizedPath, type LocaleCode } from "../../i18n/locales";
7
- import { routeSlugs } from "../../shared/route-slugs";
8
- import { getLocalizedSiteText } from "../../app/config/site.utils";
9
- import { createGinkoDocsCollections } from "../../content-collections";
10
- import { buildRssFeed } from "./feed";
11
-
12
- export const MAX_FEED_POSTS = 50;
13
- const { blog: blogCollection, authors: authorsCollection } = createGinkoDocsCollections(true);
14
-
15
- export function blogFeedPath(locale: LocaleCode): string {
16
- return `${localizedPath(locale, routeSlugs.blog[locale])}/rss.xml`;
17
- }
18
-
19
- export async function serveBlogFeed(event: H3Event, locale: LocaleCode) {
20
- const contentRuntime = useRuntimeConfig(event).public.content as
21
- | { collections?: Record<string, unknown> }
22
- | undefined;
23
- if (!contentRuntime?.collections?.blog) {
24
- throw createError({ statusCode: 404, statusMessage: "Blog is not enabled" });
25
- }
26
-
27
- const site = useAppConfig().ginkoDocs.site;
28
- const posts = await many(event, blogCollection, {
29
- locale,
30
- fallback: true,
31
- populate: { author: authorsCollection },
32
- sort: { date: "desc" },
33
- limit: MAX_FEED_POSTS,
34
- });
35
-
36
- setHeader(event, "content-type", "application/rss+xml; charset=utf-8");
37
- return buildRssFeed({
38
- title: `${blog.title[locale]} - ${getLocalizedSiteText(site.name, locale)}`,
39
- description: blog.description[locale],
40
- siteUrl: site.url,
41
- feedPath: blogFeedPath(locale),
42
- language: locales.find((entry) => entry.code === locale)?.language ?? locale,
43
- items: posts.map((post) => ({
44
- title: post.title,
45
- path: post.route.resolvedPath,
46
- description: post.description,
47
- date: post.date,
48
- authorName: post.author?.name,
49
- })),
50
- });
51
- }
@@ -1,70 +0,0 @@
1
- export interface RssFeedItem {
2
- title: string;
3
- /** Site-relative path of the post, e.g. "/blog/my-post". */
4
- path: string;
5
- description: string;
6
- /** Authored publication date, YYYY-MM-DD. */
7
- date: string;
8
- authorName?: string;
9
- }
10
-
11
- export interface RssFeedInput {
12
- title: string;
13
- description: string;
14
- /** Absolute site origin, e.g. "https://docs.example.com". */
15
- siteUrl: string;
16
- /** Site-relative path of the feed itself, e.g. "/blog/rss.xml". */
17
- feedPath: string;
18
- language: string;
19
- items: RssFeedItem[];
20
- }
21
-
22
- function escapeXml(value: string): string {
23
- return value
24
- .replaceAll("&", "&amp;")
25
- .replaceAll("<", "&lt;")
26
- .replaceAll(">", "&gt;")
27
- .replaceAll('"', "&quot;")
28
- .replaceAll("'", "&apos;");
29
- }
30
-
31
- function toRfc822(date: string): string {
32
- return new Date(`${date}T00:00:00Z`).toUTCString();
33
- }
34
-
35
- export function buildRssFeed(feed: RssFeedInput): string {
36
- const base = feed.siteUrl.replace(/\/$/, "");
37
- const newestDate = feed.items
38
- .map((item) => item.date)
39
- .sort()
40
- .at(-1);
41
-
42
- const items = feed.items
43
- .map((item) => {
44
- const link = `${base}${item.path}`;
45
- const author = item.authorName
46
- ? `\n <dc:creator>${escapeXml(item.authorName)}</dc:creator>`
47
- : "";
48
- return ` <item>
49
- <title>${escapeXml(item.title)}</title>
50
- <link>${escapeXml(link)}</link>
51
- <guid>${escapeXml(link)}</guid>
52
- <pubDate>${toRfc822(item.date)}</pubDate>
53
- <description>${escapeXml(item.description)}</description>${author}
54
- </item>`;
55
- })
56
- .join("\n");
57
-
58
- return `<?xml version="1.0" encoding="UTF-8"?>
59
- <rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
60
- <channel>
61
- <title>${escapeXml(feed.title)}</title>
62
- <link>${escapeXml(`${base}${feed.feedPath.replace(/rss\.xml$/, "")}`)}</link>
63
- <atom:link href="${escapeXml(`${base}${feed.feedPath}`)}" rel="self" type="application/rss+xml"/>
64
- <description>${escapeXml(feed.description)}</description>
65
- <language>${escapeXml(feed.language)}</language>${newestDate ? `\n <lastBuildDate>${toRfc822(newestDate)}</lastBuildDate>` : ""}
66
- ${items}
67
- </channel>
68
- </rss>
69
- `;
70
- }
@@ -1,45 +0,0 @@
1
- export interface GinkoDocsServerCardOptions {
2
- origin: string;
3
- version: string;
4
- }
5
-
6
- export function createGinkoDocsServerCard({ origin, version }: GinkoDocsServerCardOptions) {
7
- const baseUrl = new URL(origin);
8
-
9
- return {
10
- $schema: "https://static.modelcontextprotocol.io/schemas/v1/server-card.schema.json",
11
- name: "dev.lupinum/ginko-docs",
12
- title: "Ginko Docs",
13
- version,
14
- description: "Read published Ginko Docs pages through its public MCP tools.",
15
- websiteUrl: new URL("/docs/features/agent-readable-output", baseUrl).toString(),
16
- repository: {
17
- source: "github",
18
- url: "https://github.com/lupinum-dev/ginko-docs",
19
- },
20
- remotes: [
21
- {
22
- type: "streamable-http",
23
- url: new URL("/mcp", baseUrl).toString(),
24
- supportedProtocolVersions: ["2025-11-25"],
25
- },
26
- ],
27
- } as const;
28
- }
29
-
30
- export function createGinkoDocsAiCatalog(origin: string) {
31
- const baseUrl = new URL(origin);
32
-
33
- return {
34
- specVersion: "1.0",
35
- entries: [
36
- {
37
- identifier: "urn:air:ginko-docs.lupinum.com:mcp:ginko-docs",
38
- displayName: "Ginko Docs MCP Server",
39
- type: "application/mcp-server-card+json",
40
- description: "Read the public Ginko Docs documentation through MCP.",
41
- url: new URL("/mcp/server-card", baseUrl).toString(),
42
- },
43
- ],
44
- } as const;
45
- }
@@ -1,38 +0,0 @@
1
- import type { H3Event } from "h3";
2
- import { many } from "@lupinum/ginko-content/server";
3
- import { useRuntimeConfig } from "#imports";
4
- import { localeCodes } from "../../i18n/locales";
5
- import { buildRedirectMap, type RedirectSourceDocument } from "./redirects.utils";
6
-
7
- async function queryRedirectDocuments(event: H3Event): Promise<RedirectSourceDocument[]> {
8
- const contentRuntime = useRuntimeConfig(event).public.content as
9
- | { collections?: Record<string, unknown> }
10
- | undefined;
11
- const collections = ["docs", ...(contentRuntime?.collections?.blog ? ["blog"] : [])];
12
-
13
- const results = await Promise.all(
14
- collections.flatMap((collection) =>
15
- localeCodes.map((locale) =>
16
- // fallback: false — an English page resolved into a German route would
17
- // otherwise register its redirectFrom entries twice.
18
- many(event, collection, { locale, fallback: false }),
19
- ),
20
- ),
21
- );
22
- return results.flat() as RedirectSourceDocument[];
23
- }
24
-
25
- let cachedMap: Promise<Map<string, string>> | undefined;
26
-
27
- export function loadRedirectMap(event: H3Event): Promise<Map<string, string>> {
28
- if (import.meta.dev) {
29
- return queryRedirectDocuments(event).then(buildRedirectMap);
30
- }
31
- cachedMap ??= queryRedirectDocuments(event)
32
- .then(buildRedirectMap)
33
- .catch((error) => {
34
- cachedMap = undefined;
35
- throw error;
36
- });
37
- return cachedMap;
38
- }
@@ -1,58 +0,0 @@
1
- import { localeCodes, localizedPath } from "../../i18n/locales";
2
- import { routeSlugs } from "../../shared/route-slugs";
3
-
4
- export interface RedirectSourceDocument {
5
- route: { resolvedPath: string };
6
- redirectFrom?: string[];
7
- }
8
-
9
- export function normalizeRedirectPath(path: string): string {
10
- const trimmed = path.split("?")[0] ?? path;
11
- if (trimmed === "/") return "/";
12
- return trimmed.replace(/\/+$/, "");
13
- }
14
-
15
- /** Routes the theme itself owns; a redirect must never shadow one of them. */
16
- export function themeStaticRoutes(): string[] {
17
- return localeCodes.flatMap((locale) => [
18
- localizedPath(locale, routeSlugs.home[locale]),
19
- localizedPath(locale, routeSlugs.docs[locale]),
20
- localizedPath(locale, routeSlugs.blog[locale]),
21
- ]);
22
- }
23
-
24
- /**
25
- * Builds old-path → live-path pairs from every document's `redirectFrom`.
26
- * Conflicts throw so `failOnError` stops the build instead of shipping a
27
- * redirect that shadows a live page.
28
- */
29
- export function buildRedirectMap(documents: RedirectSourceDocument[]): Map<string, string> {
30
- const livePaths = new Set(
31
- documents.map((document) => normalizeRedirectPath(document.route.resolvedPath)),
32
- );
33
- const reserved = new Set(themeStaticRoutes().map(normalizeRedirectPath));
34
- const map = new Map<string, string>();
35
- const problems: string[] = [];
36
-
37
- for (const document of documents) {
38
- const target = normalizeRedirectPath(document.route.resolvedPath);
39
- for (const source of document.redirectFrom ?? []) {
40
- const from = normalizeRedirectPath(source);
41
- if (livePaths.has(from)) {
42
- problems.push(`"${from}" redirects to "${target}" but is also a live page`);
43
- } else if (reserved.has(from)) {
44
- problems.push(`"${from}" redirects to "${target}" but is a theme route`);
45
- } else if (map.has(from) && map.get(from) !== target) {
46
- problems.push(`"${from}" is claimed by both "${map.get(from)}" and "${target}"`);
47
- } else {
48
- map.set(from, target);
49
- }
50
- }
51
- }
52
-
53
- if (problems.length) {
54
- throw new Error(`Invalid redirectFrom entries:\n${problems.map((p) => `- ${p}`).join("\n")}`);
55
- }
56
-
57
- return map;
58
- }
@@ -1,45 +0,0 @@
1
- import { localeCodes, localizedPath } from "../../i18n/locales";
2
- import { routeSlugs } from "../../shared/route-slugs";
3
-
4
- interface SitemapAlternative {
5
- href: string;
6
- }
7
-
8
- export interface SitemapEntry {
9
- loc: string;
10
- alternatives?: SitemapAlternative[];
11
- }
12
-
13
- interface SitemapContentPolicy {
14
- locales: readonly string[];
15
- blogEnabled: boolean;
16
- }
17
-
18
- const pathname = (url: string) => new URL(url, "https://ginko.invalid").pathname;
19
-
20
- export function filterSitemapEntries<Entry extends SitemapEntry>(
21
- entries: Entry[],
22
- policy: SitemapContentPolicy,
23
- ): Entry[] {
24
- const disabledLocaleRoots = localeCodes
25
- .filter((locale) => !policy.locales.includes(locale))
26
- .map((locale) => `/${locale}`);
27
- const disabledBlogRoots = policy.blogEnabled
28
- ? []
29
- : localeCodes.map((locale) => localizedPath(locale, routeSlugs.blog[locale]));
30
- const excludedRoots = [...disabledLocaleRoots, ...disabledBlogRoots];
31
- const isExcluded = (url: string) => {
32
- const path = pathname(url);
33
- return excludedRoots.some((root) => path === root || path.startsWith(`${root}/`));
34
- };
35
-
36
- return entries
37
- .filter((entry) => !isExcluded(entry.loc))
38
- .map((entry) => {
39
- if (!entry.alternatives) return entry;
40
- return {
41
- ...entry,
42
- alternatives: entry.alternatives.filter((alternative) => !isExcluded(alternative.href)),
43
- };
44
- });
45
- }
@@ -1,5 +0,0 @@
1
- export const routeSlugs = {
2
- home: { en: "/", de: "/" },
3
- docs: { en: "/docs", de: "/dokumentation" },
4
- blog: { en: "/blog", de: "/blog" },
5
- } as const;
@@ -1,37 +0,0 @@
1
- export const GINKO_DOCS_NEUTRAL_PALETTES = [
2
- "slate",
3
- "gray",
4
- "zinc",
5
- "neutral",
6
- "stone",
7
- "taupe",
8
- "mauve",
9
- "mist",
10
- "olive",
11
- "custom",
12
- ] as const;
13
-
14
- export const GINKO_DOCS_PRIMARY_PALETTES = [
15
- "neutral",
16
- "red",
17
- "orange",
18
- "amber",
19
- "yellow",
20
- "lime",
21
- "green",
22
- "emerald",
23
- "teal",
24
- "cyan",
25
- "sky",
26
- "blue",
27
- "indigo",
28
- "violet",
29
- "purple",
30
- "fuchsia",
31
- "pink",
32
- "rose",
33
- "custom",
34
- ] as const;
35
-
36
- export type GinkoDocsNeutralPalette = (typeof GINKO_DOCS_NEUTRAL_PALETTES)[number];
37
- export type GinkoDocsPrimaryPalette = (typeof GINKO_DOCS_PRIMARY_PALETTES)[number];
@@ -1,3 +0,0 @@
1
- export const GINKO_DOCS_THEME_PRESETS = ["default", "nuxt"] as const;
2
-
3
- export type GinkoDocsThemePreset = (typeof GINKO_DOCS_THEME_PRESETS)[number];