@kirchdev/duxt 0.1.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 (401) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +151 -0
  3. package/app/app.config.ts +13 -0
  4. package/app/app.vue +122 -0
  5. package/app/assets/css/duxt.css +555 -0
  6. package/app/assets/css/typeset.css +498 -0
  7. package/app/components/DuxtBrand.vue +53 -0
  8. package/app/components/DuxtBreadcrumb.vue +37 -0
  9. package/app/components/DuxtCodeBlock.vue +143 -0
  10. package/app/components/DuxtCopyPage.vue +117 -0
  11. package/app/components/DuxtFooter.vue +86 -0
  12. package/app/components/DuxtHeader.vue +496 -0
  13. package/app/components/DuxtJsonEditor.vue +359 -0
  14. package/app/components/DuxtLandingProse.vue +90 -0
  15. package/app/components/DuxtLandingShowcase.vue +295 -0
  16. package/app/components/DuxtLiveWindow.vue +343 -0
  17. package/app/components/DuxtLocale.vue +86 -0
  18. package/app/components/DuxtMetaList.vue +76 -0
  19. package/app/components/DuxtNavigation.vue +94 -0
  20. package/app/components/DuxtNavigationLink.vue +41 -0
  21. package/app/components/DuxtOpenApiCallbacks.vue +92 -0
  22. package/app/components/DuxtOpenApiClient.vue +1339 -0
  23. package/app/components/DuxtOpenApiMedia.vue +108 -0
  24. package/app/components/DuxtOpenApiMethod.vue +59 -0
  25. package/app/components/DuxtOpenApiParameters.vue +114 -0
  26. package/app/components/DuxtOpenApiResponses.vue +109 -0
  27. package/app/components/DuxtOpenApiSchema.vue +365 -0
  28. package/app/components/DuxtOpenApiSecurity.vue +115 -0
  29. package/app/components/DuxtPageFeedback.vue +56 -0
  30. package/app/components/DuxtPageInfo.vue +194 -0
  31. package/app/components/DuxtPageNav.vue +93 -0
  32. package/app/components/DuxtPreviewSkeleton.vue +262 -0
  33. package/app/components/DuxtProgress.vue +45 -0
  34. package/app/components/DuxtSearch.vue +271 -0
  35. package/app/components/DuxtSections.vue +65 -0
  36. package/app/components/DuxtShortcuts.vue +46 -0
  37. package/app/components/DuxtSkipLink.vue +34 -0
  38. package/app/components/DuxtToc.vue +86 -0
  39. package/app/components/DuxtTranslationBanner.vue +64 -0
  40. package/app/components/DuxtVersion.vue +168 -0
  41. package/app/components/DuxtVersionBanner.vue +109 -0
  42. package/app/components/OgImage/Duxt.satori.vue +82 -0
  43. package/app/components/content/Accordion.vue +37 -0
  44. package/app/components/content/AccordionItem.vue +46 -0
  45. package/app/components/content/Callout.vue +58 -0
  46. package/app/components/content/ChangelogGroup.vue +91 -0
  47. package/app/components/content/ChangelogReleases.vue +520 -0
  48. package/app/components/content/CodeGroup.vue +183 -0
  49. package/app/components/content/DevtoolsPanel.vue +107 -0
  50. package/app/components/content/Field.vue +50 -0
  51. package/app/components/content/FieldGroup.vue +8 -0
  52. package/app/components/content/FileTree.vue +92 -0
  53. package/app/components/content/Mermaid.vue +70 -0
  54. package/app/components/content/OpenApiOperation.vue +185 -0
  55. package/app/components/content/OpenApiOperations.vue +72 -0
  56. package/app/components/content/OpenApiOverview.vue +302 -0
  57. package/app/components/content/PackageManagers.vue +187 -0
  58. package/app/components/content/PageCards.vue +48 -0
  59. package/app/components/content/Partial.vue +65 -0
  60. package/app/components/content/Preview.vue +173 -0
  61. package/app/components/content/ProseA.vue +55 -0
  62. package/app/components/content/ProseH2.vue +53 -0
  63. package/app/components/content/ProseH3.vue +53 -0
  64. package/app/components/content/ProseH4.vue +53 -0
  65. package/app/components/content/ProseImg.vue +87 -0
  66. package/app/components/content/ProsePre.vue +58 -0
  67. package/app/components/content/ProseTable.vue +11 -0
  68. package/app/components/content/Since.vue +21 -0
  69. package/app/components/content/Steps.vue +20 -0
  70. package/app/components/content/Tab.vue +23 -0
  71. package/app/components/content/TabGroup.vue +58 -0
  72. package/app/components/ui/alert/Alert.vue +21 -0
  73. package/app/components/ui/alert/AlertDescription.vue +22 -0
  74. package/app/components/ui/alert/AlertTitle.vue +22 -0
  75. package/app/components/ui/alert/index.ts +24 -0
  76. package/app/components/ui/badge/Badge.vue +28 -0
  77. package/app/components/ui/badge/index.ts +52 -0
  78. package/app/components/ui/breadcrumb/Breadcrumb.vue +13 -0
  79. package/app/components/ui/breadcrumb/BreadcrumbEllipsis.vue +22 -0
  80. package/app/components/ui/breadcrumb/BreadcrumbItem.vue +17 -0
  81. package/app/components/ui/breadcrumb/BreadcrumbLink.vue +24 -0
  82. package/app/components/ui/breadcrumb/BreadcrumbList.vue +22 -0
  83. package/app/components/ui/breadcrumb/BreadcrumbPage.vue +20 -0
  84. package/app/components/ui/breadcrumb/BreadcrumbSeparator.vue +21 -0
  85. package/app/components/ui/breadcrumb/index.ts +7 -0
  86. package/app/components/ui/button/Button.vue +31 -0
  87. package/app/components/ui/button/index.ts +39 -0
  88. package/app/components/ui/card/Card.vue +22 -0
  89. package/app/components/ui/card/CardAction.vue +22 -0
  90. package/app/components/ui/card/CardContent.vue +14 -0
  91. package/app/components/ui/card/CardDescription.vue +17 -0
  92. package/app/components/ui/card/CardFooter.vue +17 -0
  93. package/app/components/ui/card/CardHeader.vue +22 -0
  94. package/app/components/ui/card/CardTitle.vue +17 -0
  95. package/app/components/ui/card/index.ts +7 -0
  96. package/app/components/ui/collapsible/Collapsible.vue +19 -0
  97. package/app/components/ui/collapsible/CollapsibleContent.vue +12 -0
  98. package/app/components/ui/collapsible/CollapsibleTrigger.vue +12 -0
  99. package/app/components/ui/collapsible/index.ts +3 -0
  100. package/app/components/ui/command/Command.vue +98 -0
  101. package/app/components/ui/command/CommandDialog.vue +42 -0
  102. package/app/components/ui/command/CommandEmpty.vue +31 -0
  103. package/app/components/ui/command/CommandGroup.vue +52 -0
  104. package/app/components/ui/command/CommandInput.vue +45 -0
  105. package/app/components/ui/command/CommandItem.vue +85 -0
  106. package/app/components/ui/command/CommandList.vue +32 -0
  107. package/app/components/ui/command/CommandSeparator.vue +23 -0
  108. package/app/components/ui/command/CommandShortcut.vue +19 -0
  109. package/app/components/ui/command/index.ts +29 -0
  110. package/app/components/ui/dialog/Dialog.vue +15 -0
  111. package/app/components/ui/dialog/DialogClose.vue +12 -0
  112. package/app/components/ui/dialog/DialogContent.vue +61 -0
  113. package/app/components/ui/dialog/DialogDescription.vue +25 -0
  114. package/app/components/ui/dialog/DialogFooter.vue +30 -0
  115. package/app/components/ui/dialog/DialogHeader.vue +17 -0
  116. package/app/components/ui/dialog/DialogOverlay.vue +28 -0
  117. package/app/components/ui/dialog/DialogScrollContent.vue +65 -0
  118. package/app/components/ui/dialog/DialogTitle.vue +25 -0
  119. package/app/components/ui/dialog/DialogTrigger.vue +12 -0
  120. package/app/components/ui/dialog/index.ts +10 -0
  121. package/app/components/ui/dropdown-menu/DropdownMenu.vue +19 -0
  122. package/app/components/ui/dropdown-menu/DropdownMenuCheckboxItem.vue +47 -0
  123. package/app/components/ui/dropdown-menu/DropdownMenuContent.vue +47 -0
  124. package/app/components/ui/dropdown-menu/DropdownMenuGroup.vue +12 -0
  125. package/app/components/ui/dropdown-menu/DropdownMenuItem.vue +41 -0
  126. package/app/components/ui/dropdown-menu/DropdownMenuLabel.vue +27 -0
  127. package/app/components/ui/dropdown-menu/DropdownMenuRadioGroup.vue +21 -0
  128. package/app/components/ui/dropdown-menu/DropdownMenuRadioItem.vue +48 -0
  129. package/app/components/ui/dropdown-menu/DropdownMenuSeparator.vue +23 -0
  130. package/app/components/ui/dropdown-menu/DropdownMenuShortcut.vue +19 -0
  131. package/app/components/ui/dropdown-menu/DropdownMenuSub.vue +19 -0
  132. package/app/components/ui/dropdown-menu/DropdownMenuSubContent.vue +34 -0
  133. package/app/components/ui/dropdown-menu/DropdownMenuSubTrigger.vue +34 -0
  134. package/app/components/ui/dropdown-menu/DropdownMenuTrigger.vue +17 -0
  135. package/app/components/ui/dropdown-menu/index.ts +16 -0
  136. package/app/components/ui/input/Input.vue +35 -0
  137. package/app/components/ui/input/index.ts +1 -0
  138. package/app/components/ui/navigation-menu/NavigationMenu.vue +42 -0
  139. package/app/components/ui/navigation-menu/NavigationMenuContent.vue +35 -0
  140. package/app/components/ui/navigation-menu/NavigationMenuIndicator.vue +32 -0
  141. package/app/components/ui/navigation-menu/NavigationMenuItem.vue +23 -0
  142. package/app/components/ui/navigation-menu/NavigationMenuLink.vue +30 -0
  143. package/app/components/ui/navigation-menu/NavigationMenuList.vue +30 -0
  144. package/app/components/ui/navigation-menu/NavigationMenuTrigger.vue +31 -0
  145. package/app/components/ui/navigation-menu/NavigationMenuViewport.vue +30 -0
  146. package/app/components/ui/navigation-menu/index.ts +14 -0
  147. package/app/components/ui/scroll-area/ScrollArea.vue +31 -0
  148. package/app/components/ui/scroll-area/ScrollBar.vue +38 -0
  149. package/app/components/ui/scroll-area/index.ts +2 -0
  150. package/app/components/ui/select/Select.vue +15 -0
  151. package/app/components/ui/select/SelectContent.vue +59 -0
  152. package/app/components/ui/select/SelectGroup.vue +19 -0
  153. package/app/components/ui/select/SelectItem.vue +42 -0
  154. package/app/components/ui/select/SelectItemText.vue +12 -0
  155. package/app/components/ui/select/SelectLabel.vue +16 -0
  156. package/app/components/ui/select/SelectScrollDownButton.vue +28 -0
  157. package/app/components/ui/select/SelectScrollUpButton.vue +28 -0
  158. package/app/components/ui/select/SelectSeparator.vue +20 -0
  159. package/app/components/ui/select/SelectTrigger.vue +32 -0
  160. package/app/components/ui/select/SelectValue.vue +12 -0
  161. package/app/components/ui/select/index.ts +11 -0
  162. package/app/components/ui/separator/Separator.vue +30 -0
  163. package/app/components/ui/separator/index.ts +1 -0
  164. package/app/components/ui/sheet/Sheet.vue +15 -0
  165. package/app/components/ui/sheet/SheetClose.vue +12 -0
  166. package/app/components/ui/sheet/SheetContent.vue +68 -0
  167. package/app/components/ui/sheet/SheetDescription.vue +23 -0
  168. package/app/components/ui/sheet/SheetFooter.vue +15 -0
  169. package/app/components/ui/sheet/SheetHeader.vue +15 -0
  170. package/app/components/ui/sheet/SheetOverlay.vue +28 -0
  171. package/app/components/ui/sheet/SheetTitle.vue +23 -0
  172. package/app/components/ui/sheet/SheetTrigger.vue +12 -0
  173. package/app/components/ui/sheet/index.ts +8 -0
  174. package/app/components/ui/sidebar/Sidebar.vue +110 -0
  175. package/app/components/ui/sidebar/SidebarContent.vue +23 -0
  176. package/app/components/ui/sidebar/SidebarFooter.vue +18 -0
  177. package/app/components/ui/sidebar/SidebarGroup.vue +18 -0
  178. package/app/components/ui/sidebar/SidebarGroupAction.vue +31 -0
  179. package/app/components/ui/sidebar/SidebarGroupContent.vue +18 -0
  180. package/app/components/ui/sidebar/SidebarGroupLabel.vue +30 -0
  181. package/app/components/ui/sidebar/SidebarHeader.vue +18 -0
  182. package/app/components/ui/sidebar/SidebarInput.vue +19 -0
  183. package/app/components/ui/sidebar/SidebarInset.vue +23 -0
  184. package/app/components/ui/sidebar/SidebarMenu.vue +18 -0
  185. package/app/components/ui/sidebar/SidebarMenuAction.vue +42 -0
  186. package/app/components/ui/sidebar/SidebarMenuBadge.vue +28 -0
  187. package/app/components/ui/sidebar/SidebarMenuButton.vue +60 -0
  188. package/app/components/ui/sidebar/SidebarMenuButtonChild.vue +36 -0
  189. package/app/components/ui/sidebar/SidebarMenuItem.vue +18 -0
  190. package/app/components/ui/sidebar/SidebarMenuSkeleton.vue +35 -0
  191. package/app/components/ui/sidebar/SidebarMenuSub.vue +24 -0
  192. package/app/components/ui/sidebar/SidebarMenuSubButton.vue +43 -0
  193. package/app/components/ui/sidebar/SidebarMenuSubItem.vue +18 -0
  194. package/app/components/ui/sidebar/SidebarProvider.vue +109 -0
  195. package/app/components/ui/sidebar/SidebarRail.vue +35 -0
  196. package/app/components/ui/sidebar/SidebarSeparator.vue +19 -0
  197. package/app/components/ui/sidebar/SidebarTrigger.vue +26 -0
  198. package/app/components/ui/sidebar/index.ts +62 -0
  199. package/app/components/ui/sidebar/utils.ts +19 -0
  200. package/app/components/ui/skeleton/Skeleton.vue +17 -0
  201. package/app/components/ui/skeleton/index.ts +1 -0
  202. package/app/components/ui/sonner/Sonner.vue +67 -0
  203. package/app/components/ui/sonner/index.ts +1 -0
  204. package/app/components/ui/tabs/Tabs.vue +26 -0
  205. package/app/components/ui/tabs/TabsContent.vue +23 -0
  206. package/app/components/ui/tabs/TabsList.vue +28 -0
  207. package/app/components/ui/tabs/TabsTrigger.vue +30 -0
  208. package/app/components/ui/tabs/index.ts +4 -0
  209. package/app/components/ui/textarea/Textarea.vue +32 -0
  210. package/app/components/ui/textarea/index.ts +1 -0
  211. package/app/components/ui/tooltip/Tooltip.vue +15 -0
  212. package/app/components/ui/tooltip/TooltipContent.vue +49 -0
  213. package/app/components/ui/tooltip/TooltipProvider.vue +14 -0
  214. package/app/components/ui/tooltip/TooltipTrigger.vue +12 -0
  215. package/app/components/ui/tooltip/index.ts +4 -0
  216. package/app/composables/useActiveHeading.ts +90 -0
  217. package/app/composables/useDuxtAnimatedHeight.ts +155 -0
  218. package/app/composables/useDuxtBreadcrumb.ts +60 -0
  219. package/app/composables/useDuxtChoice.ts +28 -0
  220. package/app/composables/useDuxtCollection.ts +89 -0
  221. package/app/composables/useDuxtConfig.ts +47 -0
  222. package/app/composables/useDuxtNavigation.ts +87 -0
  223. package/app/composables/useDuxtPageFocus.ts +30 -0
  224. package/app/composables/useDuxtPath.ts +42 -0
  225. package/app/composables/useDuxtSearch.ts +208 -0
  226. package/app/composables/useDuxtSection.ts +44 -0
  227. package/app/composables/useDuxtSectionRow.ts +49 -0
  228. package/app/composables/useDuxtShortcuts.ts +58 -0
  229. package/app/composables/useDuxtSiteUrl.ts +27 -0
  230. package/app/composables/useDuxtToast.ts +25 -0
  231. package/app/composables/useDuxtVersion.ts +138 -0
  232. package/app/composables/useFuzzySearch.ts +83 -0
  233. package/app/composables/usePackageManager.ts +4 -0
  234. package/app/composables/useRecentPages.ts +52 -0
  235. package/app/error.vue +133 -0
  236. package/app/layouts/changelog.vue +85 -0
  237. package/app/layouts/default.vue +23 -0
  238. package/app/layouts/docs.vue +62 -0
  239. package/app/layouts/reference.vue +85 -0
  240. package/app/lib/utils.ts +7 -0
  241. package/app/middleware/duxt-section-layout.global.ts +57 -0
  242. package/app/pages/[...slug].vue +441 -0
  243. package/app/pages/index.vue +479 -0
  244. package/app/types/duxt.d.ts +955 -0
  245. package/app/utils/aside.ts +16 -0
  246. package/app/utils/changelog.ts +182 -0
  247. package/app/utils/code-highlight.ts +116 -0
  248. package/app/utils/duxt-config.ts +185 -0
  249. package/app/utils/duxt-text.ts +135 -0
  250. package/app/utils/file-icons.ts +62 -0
  251. package/app/utils/generated-sections.ts +190 -0
  252. package/app/utils/generated-toc.ts +216 -0
  253. package/app/utils/heading-link.ts +32 -0
  254. package/app/utils/locale-path.ts +45 -0
  255. package/app/utils/navigation-tree.ts +206 -0
  256. package/app/utils/nearest-page.ts +72 -0
  257. package/app/utils/openapi.ts +1028 -0
  258. package/app/utils/package-command.ts +162 -0
  259. package/app/utils/request-samples.ts +635 -0
  260. package/app/utils/section-scope.ts +97 -0
  261. package/app/utils/sheet-navigation.ts +100 -0
  262. package/app/utils/source-file.ts +84 -0
  263. package/app/utils/version-choices.ts +68 -0
  264. package/app/utils/version-paths.ts +65 -0
  265. package/bin/duxt-report.mjs +45 -0
  266. package/components.json +19 -0
  267. package/content-cache.ts +193 -0
  268. package/content.config.ts +38 -0
  269. package/duxt-app-config.ts +124 -0
  270. package/highlight-langs.ts +68 -0
  271. package/i18n/i18n.config.ts +16 -0
  272. package/i18n/locales/de/duxt/changelog.json +17 -0
  273. package/i18n/locales/de/duxt/code.json +13 -0
  274. package/i18n/locales/de/duxt/defaults.json +22 -0
  275. package/i18n/locales/de/duxt/devtools.json +7 -0
  276. package/i18n/locales/de/duxt/error.json +10 -0
  277. package/i18n/locales/de/duxt/footer.json +7 -0
  278. package/i18n/locales/de/duxt/locale.json +7 -0
  279. package/i18n/locales/de/duxt/nav.json +17 -0
  280. package/i18n/locales/de/duxt/openapi.json +89 -0
  281. package/i18n/locales/de/duxt/page.json +34 -0
  282. package/i18n/locales/de/duxt/search.json +12 -0
  283. package/i18n/locales/de/duxt/shortcuts.json +11 -0
  284. package/i18n/locales/de/duxt/theme.json +7 -0
  285. package/i18n/locales/de/duxt/toc.json +7 -0
  286. package/i18n/locales/de/duxt/version.json +25 -0
  287. package/i18n/locales/en/duxt/changelog.json +17 -0
  288. package/i18n/locales/en/duxt/code.json +13 -0
  289. package/i18n/locales/en/duxt/defaults.json +22 -0
  290. package/i18n/locales/en/duxt/devtools.json +7 -0
  291. package/i18n/locales/en/duxt/error.json +10 -0
  292. package/i18n/locales/en/duxt/footer.json +7 -0
  293. package/i18n/locales/en/duxt/locale.json +7 -0
  294. package/i18n/locales/en/duxt/nav.json +17 -0
  295. package/i18n/locales/en/duxt/openapi.json +89 -0
  296. package/i18n/locales/en/duxt/page.json +34 -0
  297. package/i18n/locales/en/duxt/search.json +12 -0
  298. package/i18n/locales/en/duxt/shortcuts.json +11 -0
  299. package/i18n/locales/en/duxt/theme.json +7 -0
  300. package/i18n/locales/en/duxt/toc.json +7 -0
  301. package/i18n/locales/en/duxt/version.json +25 -0
  302. package/i18n/locales/es/duxt/changelog.json +17 -0
  303. package/i18n/locales/es/duxt/code.json +13 -0
  304. package/i18n/locales/es/duxt/defaults.json +22 -0
  305. package/i18n/locales/es/duxt/devtools.json +7 -0
  306. package/i18n/locales/es/duxt/error.json +10 -0
  307. package/i18n/locales/es/duxt/footer.json +7 -0
  308. package/i18n/locales/es/duxt/locale.json +7 -0
  309. package/i18n/locales/es/duxt/nav.json +17 -0
  310. package/i18n/locales/es/duxt/openapi.json +89 -0
  311. package/i18n/locales/es/duxt/page.json +34 -0
  312. package/i18n/locales/es/duxt/search.json +12 -0
  313. package/i18n/locales/es/duxt/shortcuts.json +11 -0
  314. package/i18n/locales/es/duxt/theme.json +7 -0
  315. package/i18n/locales/es/duxt/toc.json +7 -0
  316. package/i18n/locales/es/duxt/version.json +25 -0
  317. package/i18n/locales/fr/duxt/changelog.json +17 -0
  318. package/i18n/locales/fr/duxt/code.json +13 -0
  319. package/i18n/locales/fr/duxt/defaults.json +22 -0
  320. package/i18n/locales/fr/duxt/devtools.json +7 -0
  321. package/i18n/locales/fr/duxt/error.json +10 -0
  322. package/i18n/locales/fr/duxt/footer.json +7 -0
  323. package/i18n/locales/fr/duxt/locale.json +7 -0
  324. package/i18n/locales/fr/duxt/nav.json +17 -0
  325. package/i18n/locales/fr/duxt/openapi.json +89 -0
  326. package/i18n/locales/fr/duxt/page.json +34 -0
  327. package/i18n/locales/fr/duxt/search.json +12 -0
  328. package/i18n/locales/fr/duxt/shortcuts.json +11 -0
  329. package/i18n/locales/fr/duxt/theme.json +7 -0
  330. package/i18n/locales/fr/duxt/toc.json +7 -0
  331. package/i18n/locales/fr/duxt/version.json +25 -0
  332. package/i18n/locales/pt/duxt/changelog.json +17 -0
  333. package/i18n/locales/pt/duxt/code.json +13 -0
  334. package/i18n/locales/pt/duxt/defaults.json +22 -0
  335. package/i18n/locales/pt/duxt/devtools.json +7 -0
  336. package/i18n/locales/pt/duxt/error.json +10 -0
  337. package/i18n/locales/pt/duxt/footer.json +7 -0
  338. package/i18n/locales/pt/duxt/locale.json +7 -0
  339. package/i18n/locales/pt/duxt/nav.json +17 -0
  340. package/i18n/locales/pt/duxt/openapi.json +89 -0
  341. package/i18n/locales/pt/duxt/page.json +34 -0
  342. package/i18n/locales/pt/duxt/search.json +12 -0
  343. package/i18n/locales/pt/duxt/shortcuts.json +11 -0
  344. package/i18n/locales/pt/duxt/theme.json +7 -0
  345. package/i18n/locales/pt/duxt/toc.json +7 -0
  346. package/i18n/locales/pt/duxt/version.json +25 -0
  347. package/i18n/locales/pt-BR/duxt/error.json +8 -0
  348. package/i18n/locales/pt-BR/duxt/nav.json +10 -0
  349. package/i18n/locales/pt-BR/duxt/page.json +10 -0
  350. package/i18n/locales/pt-BR/duxt/search.json +11 -0
  351. package/i18n/locales/pt-BR/duxt/version.json +13 -0
  352. package/mdc.config.ts +28 -0
  353. package/modules/config.ts +440 -0
  354. package/modules/devtools.ts +126 -0
  355. package/modules/git-meta.ts +349 -0
  356. package/modules/redirects.ts +152 -0
  357. package/modules/validate.ts +137 -0
  358. package/nuxt.config.ts +530 -0
  359. package/openapi-model.ts +358 -0
  360. package/openapi-parse.ts +1052 -0
  361. package/package.json +161 -0
  362. package/public/devtools/cache.html +318 -0
  363. package/public/devtools/checks.html +309 -0
  364. package/public/devtools/config.html +461 -0
  365. package/public/devtools/i18n.html +309 -0
  366. package/public/devtools/pages.html +348 -0
  367. package/public/devtools/paths.html +315 -0
  368. package/public/devtools/redirects.html +309 -0
  369. package/public/devtools/search.html +324 -0
  370. package/public/devtools/sources.html +309 -0
  371. package/public/devtools/versions.html +318 -0
  372. package/report.ts +303 -0
  373. package/repository-root.ts +26 -0
  374. package/section-reports.ts +55 -0
  375. package/sections-changelog.ts +644 -0
  376. package/sections-openapi.ts +450 -0
  377. package/sections-resolve.ts +711 -0
  378. package/sections.ts +157 -0
  379. package/server/devtools/content.ts +120 -0
  380. package/server/devtools/context.ts +38 -0
  381. package/server/devtools/entry-path.ts +30 -0
  382. package/server/devtools/handler.ts +104 -0
  383. package/server/devtools/preview.ts +461 -0
  384. package/server/devtools/render/content.ts +233 -0
  385. package/server/devtools/render/sources.ts +414 -0
  386. package/server/devtools/render/system.ts +352 -0
  387. package/server/devtools/shell.ts +611 -0
  388. package/server/devtools/sources.ts +122 -0
  389. package/server/devtools/system.ts +178 -0
  390. package/server/mcp/tools/list-pages.ts +52 -0
  391. package/server/mcp/tools/read-page.ts +57 -0
  392. package/server/mcp/tools/search-docs.ts +74 -0
  393. package/server/middleware/raw-markdown.ts +86 -0
  394. package/server/routes/llms-full.txt.get.ts +75 -0
  395. package/server/routes/llms.txt.get.ts +63 -0
  396. package/server/routes/rss.xml.get.ts +125 -0
  397. package/server/utils/duxt-server-text.ts +46 -0
  398. package/sources-git.ts +113 -0
  399. package/sources-resolve.ts +908 -0
  400. package/sources.ts +287 -0
  401. package/validate-report.ts +494 -0
@@ -0,0 +1,122 @@
1
+ import type { H3Event } from 'h3';
2
+ import { queryCollection } from '@nuxt/content/nitro';
3
+ import { splitLocalePath } from '../../app/utils/locale-path';
4
+ import { sourcesForRoute } from '../../sources-resolve';
5
+ import { context, resolvedSources } from './context';
6
+ import type { FoundPage } from './render/sources';
7
+ import { renderPaths, renderSources, renderVersions } from './render/sources';
8
+
9
+ /**
10
+ * The queries behind the three source panels.
11
+ *
12
+ * The resolver is pure and already tested, so none of this reimplements it: the
13
+ * panels call the same functions the theme calls, and hand what came back to
14
+ * `render/sources.ts`. A panel that computed its own answer would be able to
15
+ * disagree with the site, which is the one thing a debugging view must not do.
16
+ */
17
+
18
+ /** Content stores a page under its full path, prefix included. */
19
+ const collectionsOf = () =>
20
+ resolvedSources().map((source) => source.collection) as Parameters<
21
+ typeof queryCollection
22
+ >[1][];
23
+
24
+ /** Every page path the site serves, by collection. */
25
+ async function pathsByCollection(
26
+ event: H3Event
27
+ ): Promise<Map<string, string[]>> {
28
+ const result = new Map<string, string[]>();
29
+
30
+ await Promise.all(
31
+ collectionsOf().map(async (name) => {
32
+ try {
33
+ const pages = await queryCollection(event, name)
34
+ .select('path')
35
+ .order('path', 'ASC')
36
+ .all();
37
+
38
+ result.set(
39
+ name,
40
+ pages.map((page) => page.path)
41
+ );
42
+ } catch {
43
+ // A collection Content dropped is exactly what the Checks panel is for;
44
+ // here it is simply a collection with no pages.
45
+ result.set(name, []);
46
+ }
47
+ })
48
+ );
49
+
50
+ return result;
51
+ }
52
+
53
+ /** The sources table — what a consumer wrote, as the site resolved it. */
54
+ export function sourcesPanel(): string {
55
+ return renderSources(resolvedSources(), context.appConfigFile);
56
+ }
57
+
58
+ /** The path debugger: which source claims a URL, and what it finds there. */
59
+ export async function pathsPanel(
60
+ event: H3Event,
61
+ input: string
62
+ ): Promise<string> {
63
+ const sources = resolvedSources();
64
+ const locales = context.locales;
65
+
66
+ if (!input) {
67
+ return renderPaths({
68
+ input,
69
+ locales,
70
+ fallbackLocale: context.fallbackLocale,
71
+ sources,
72
+ found: null,
73
+ byCollection: new Map()
74
+ });
75
+ }
76
+
77
+ // The same two pure calls the renderer walks the reader through, repeated
78
+ // here only to know WHICH collection to query. Cheap, and it keeps the
79
+ // renderer free of the database.
80
+ //
81
+ // The locale is carried into the choice rather than thrown away with the
82
+ // segment: a translation lives under the SAME prefix as its original, so a
83
+ // lookup given only the stripped path answers with whichever collection
84
+ // sorted first — and told the reader about `docs` when they pasted a `/de/`
85
+ // URL.
86
+ const { locale, path: documentationPath } = splitLocalePath(input, locales);
87
+ const source = sourcesForRoute(
88
+ documentationPath,
89
+ locale,
90
+ sources,
91
+ context.fallbackLocale
92
+ )[0];
93
+
94
+ let found: FoundPage | null = null;
95
+
96
+ if (source) {
97
+ try {
98
+ found = (await queryCollection(
99
+ event,
100
+ source.collection as Parameters<typeof queryCollection>[1]
101
+ )
102
+ .path(documentationPath)
103
+ .first()) as unknown as FoundPage | null;
104
+ } catch {
105
+ found = null;
106
+ }
107
+ }
108
+
109
+ return renderPaths({
110
+ input,
111
+ locales,
112
+ fallbackLocale: context.fallbackLocale,
113
+ sources,
114
+ found,
115
+ byCollection: await pathsByCollection(event)
116
+ });
117
+ }
118
+
119
+ /** Which version — and which language — carries which page. */
120
+ export async function versionsPanel(event: H3Event): Promise<string> {
121
+ return renderVersions(resolvedSources(), await pathsByCollection(event));
122
+ }
@@ -0,0 +1,178 @@
1
+ import { readdirSync, readFileSync, statSync } from 'node:fs';
2
+ import { join } from 'node:path';
3
+ import { context } from './context';
4
+ import type { CacheEntry, LocaleFile, RedirectRule } from './render/system';
5
+ import {
6
+ renderCache,
7
+ renderConfig,
8
+ renderI18n,
9
+ renderRedirects
10
+ } from './render/system';
11
+
12
+ /**
13
+ * What the panels about the machinery read: the merged config, the message
14
+ * catalogues, Content's download cache and the redirects that shipped.
15
+ *
16
+ * Each answers a question whose answer exists only as a side effect somewhere —
17
+ * inside a defu call, across seven JSON files, in a directory nobody opens, in
18
+ * a route-rule table generated at build time. This half does the reading;
19
+ * `render/system.ts` turns it into tables.
20
+ */
21
+
22
+ /* -------------------------------------------------------------------------- */
23
+ /* Config */
24
+ /* -------------------------------------------------------------------------- */
25
+
26
+ /** The merged config, with the side of the merge each value came from. */
27
+ export function configPanel(): string {
28
+ const { duxt } = useAppConfig() as { duxt?: Partial<DuxtConfig> };
29
+
30
+ return renderConfig((duxt ?? {}) as unknown as Record<string, unknown>);
31
+ }
32
+
33
+ /* -------------------------------------------------------------------------- */
34
+ /* i18n */
35
+ /* -------------------------------------------------------------------------- */
36
+
37
+ /** Every message key of every locale file the layer ships. */
38
+ export function i18nPanel(): string {
39
+ const root = join(context.layerDir, 'i18n/locales');
40
+
41
+ let dirs: string[];
42
+ try {
43
+ dirs = readdirSync(root, { withFileTypes: true })
44
+ .filter((entry) => entry.isDirectory())
45
+ .map((entry) => entry.name)
46
+ .sort();
47
+ } catch {
48
+ return '<div class="note">No <code>i18n/locales</code> directory in the layer.</div>';
49
+ }
50
+
51
+ const files: LocaleFile[] = dirs.map((dir) => ({
52
+ dir,
53
+ keys: [...keysOf(join(root, dir, 'duxt'))]
54
+ }));
55
+
56
+ return renderI18n({
57
+ files,
58
+ locales: context.locales,
59
+ defaultLocale: context.defaultLocale
60
+ });
61
+ }
62
+
63
+ function keysOf(namespaces: string): Set<string> {
64
+ const keys = new Set<string>();
65
+
66
+ let files: string[];
67
+ try {
68
+ files = readdirSync(namespaces).filter((file) => file.endsWith('.json'));
69
+ } catch {
70
+ return keys;
71
+ }
72
+
73
+ for (const file of files) {
74
+ try {
75
+ flatten(
76
+ JSON.parse(readFileSync(join(namespaces, file), 'utf8')) as unknown,
77
+ '',
78
+ keys
79
+ );
80
+ } catch {
81
+ keys.add(`${file}: unreadable`);
82
+ }
83
+ }
84
+
85
+ return keys;
86
+ }
87
+
88
+ function flatten(node: unknown, prefix: string, out: Set<string>): void {
89
+ if (!node || typeof node !== 'object' || Array.isArray(node)) {
90
+ if (prefix) out.add(prefix);
91
+ return;
92
+ }
93
+
94
+ for (const [key, value] of Object.entries(node as Record<string, unknown>)) {
95
+ flatten(value, prefix ? `${prefix}.${key}` : key, out);
96
+ }
97
+ }
98
+
99
+ /* -------------------------------------------------------------------------- */
100
+ /* Cache */
101
+ /* -------------------------------------------------------------------------- */
102
+
103
+ /** Content's download cache, and a way to drop one entry. */
104
+ export function cachePanel(message?: string): string {
105
+ let entries: CacheEntry[] | undefined;
106
+
107
+ try {
108
+ entries = readdirSync(context.dataDir, { withFileTypes: true })
109
+ .filter((entry) => entry.isDirectory())
110
+ .map((entry) => {
111
+ const path = join(context.dataDir, entry.name);
112
+ return {
113
+ name: entry.name,
114
+ size: sizeOf(path),
115
+ modified: statSync(path).mtime
116
+ };
117
+ })
118
+ .sort((a, b) => a.name.localeCompare(b.name));
119
+ } catch {
120
+ entries = undefined;
121
+ }
122
+
123
+ return renderCache({ dataDir: context.dataDir, entries, message });
124
+ }
125
+
126
+ function sizeOf(path: string): number {
127
+ let total = 0;
128
+
129
+ for (const entry of readdirSync(path, { withFileTypes: true })) {
130
+ const child = join(path, entry.name);
131
+ total += entry.isDirectory() ? sizeOf(child) : statSync(child).size;
132
+ }
133
+
134
+ return total;
135
+ }
136
+
137
+ /* -------------------------------------------------------------------------- */
138
+ /* Redirects */
139
+ /* -------------------------------------------------------------------------- */
140
+
141
+ /**
142
+ * Every redirect the server ships, and which of them the layer wrote.
143
+ *
144
+ * Read rather than recomputed on purpose: a rule that was generated and a rule
145
+ * that shipped are different claims, and only the second one redirects anybody.
146
+ * Reading them back means reading EVERY module's, so `modules/redirects.ts`
147
+ * records which paths are its own beside them — nothing in a route rule says
148
+ * who wrote it.
149
+ */
150
+ export function redirectsPanel(): string {
151
+ const config = useRuntimeConfig() as {
152
+ nitro?: { routeRules?: Record<string, { redirect?: unknown }> };
153
+ duxt?: { redirects?: string[] };
154
+ };
155
+
156
+ const rules = config.nitro?.routeRules;
157
+ if (!rules) return renderRedirects();
158
+
159
+ const ours = new Set(config.duxt?.redirects ?? []);
160
+
161
+ const redirects: RedirectRule[] = Object.entries(rules)
162
+ .filter(([, rule]) => rule?.redirect)
163
+ .map(([from, rule]) => {
164
+ const redirect = rule.redirect as
165
+ | string
166
+ | { to?: string; statusCode?: number };
167
+
168
+ return {
169
+ from,
170
+ to: typeof redirect === 'string' ? redirect : (redirect.to ?? '?'),
171
+ status:
172
+ typeof redirect === 'string' ? '302' : (redirect.statusCode ?? 302),
173
+ mine: ours.has(from)
174
+ };
175
+ });
176
+
177
+ return renderRedirects(redirects);
178
+ }
@@ -0,0 +1,52 @@
1
+ import { queryCollection } from '@nuxt/content/nitro';
2
+
3
+ /**
4
+ * The documentation's table of contents, for a model.
5
+ *
6
+ * Built on @nuxtjs/mcp-toolkit rather than a JSON endpoint of our own: it
7
+ * speaks the actual protocol through the official SDK, so a client connects to
8
+ * `/mcp` instead of someone writing a server around our shape.
9
+ */
10
+ /** Every collection the manifest names — a versioned site has none called `docs`. */
11
+ function duxtCollections(): 'docs'[] {
12
+ const { duxt } = useAppConfig() as { duxt?: Partial<DuxtConfig> };
13
+ const names = duxt?.resolvedSources?.length
14
+ ? duxt.resolvedSources.map((source) => source.collection)
15
+ : ['docs'];
16
+ return names as 'docs'[];
17
+ }
18
+
19
+ export default defineMcpTool({
20
+ name: 'list_pages',
21
+ title: 'List documentation pages',
22
+ description:
23
+ 'Every page of this documentation with its path, title and description. ' +
24
+ 'Start here, then read a page with `read_page`.',
25
+ annotations: { readOnlyHint: true },
26
+
27
+ // A tool without an `inputSchema` is called with ONE argument: the SDK hands
28
+ // the handler `extra` directly rather than `(args, extra)`. Written the other
29
+ // way round, `extra` was undefined and every call failed on `extra.event`.
30
+ async handler(extra) {
31
+ const pages = (
32
+ await Promise.all(
33
+ duxtCollections().map((name) =>
34
+ queryCollection(extra.event, name)
35
+ .select('path', 'title', 'description')
36
+ .all()
37
+ )
38
+ )
39
+ ).flat();
40
+
41
+ const listing = pages
42
+ .filter((page) => page.path)
43
+ .sort((a, b) => a.path!.localeCompare(b.path!))
44
+ .map(
45
+ (page) =>
46
+ `- ${page.path} — ${page.title}${page.description ? `: ${page.description}` : ''}`
47
+ )
48
+ .join('\n');
49
+
50
+ return { content: [{ type: 'text', text: listing }] };
51
+ }
52
+ });
@@ -0,0 +1,57 @@
1
+ import { queryCollection } from '@nuxt/content/nitro';
2
+ import { z } from 'zod';
3
+
4
+ /**
5
+ * One page's prose. Returns the stored content rather than rendered HTML — a
6
+ * model reading documentation wants the text, not the theme around it.
7
+ */
8
+ /** Every collection the manifest names — a versioned site has none called `docs`. */
9
+ function duxtCollections(): 'docs'[] {
10
+ const { duxt } = useAppConfig() as { duxt?: Partial<DuxtConfig> };
11
+ const names = duxt?.resolvedSources?.length
12
+ ? duxt.resolvedSources.map((source) => source.collection)
13
+ : ['docs'];
14
+ return names as 'docs'[];
15
+ }
16
+
17
+ export default defineMcpTool({
18
+ name: 'read_page',
19
+ title: 'Read a documentation page',
20
+ description: 'The full content of one page. Paths come from `list_pages`.',
21
+ annotations: { readOnlyHint: true },
22
+
23
+ inputSchema: {
24
+ path: z.string().describe('Page path, for example /guide/deploying')
25
+ },
26
+
27
+ async handler({ path }, extra) {
28
+ // Try each collection: a path carries its own prefix, so at most one has it.
29
+ const found = await Promise.all(
30
+ duxtCollections().map((name) =>
31
+ queryCollection(extra.event, name).path(path).first()
32
+ )
33
+ );
34
+ const page = found.find(Boolean);
35
+
36
+ if (!page) {
37
+ return {
38
+ content: [
39
+ {
40
+ type: 'text',
41
+ text: `No page at ${path}. Use \`list_pages\` for valid paths.`
42
+ }
43
+ ],
44
+ isError: true
45
+ };
46
+ }
47
+
48
+ return {
49
+ content: [
50
+ {
51
+ type: 'text',
52
+ text: `# ${page.title}\n\n${page.description ?? ''}\n\n${JSON.stringify(page.body)}`
53
+ }
54
+ ]
55
+ };
56
+ }
57
+ });
@@ -0,0 +1,74 @@
1
+ import { queryCollection } from '@nuxt/content/nitro';
2
+ import { z } from 'zod';
3
+
4
+ /**
5
+ * Search, so a model does not have to read every page to find one.
6
+ *
7
+ * A LIKE over title and description rather than the FTS index the browser
8
+ * uses: that index is built client-side from the collection dump, and a server
9
+ * tool has the database itself.
10
+ */
11
+ /** Every collection the manifest names — a versioned site has none called `docs`. */
12
+ function duxtCollections(): 'docs'[] {
13
+ const { duxt } = useAppConfig() as { duxt?: Partial<DuxtConfig> };
14
+ const names = duxt?.resolvedSources?.length
15
+ ? duxt.resolvedSources.map((source) => source.collection)
16
+ : ['docs'];
17
+ return names as 'docs'[];
18
+ }
19
+
20
+ export default defineMcpTool({
21
+ name: 'search_docs',
22
+ title: 'Search the documentation',
23
+ description: 'Find pages whose title or description matches a term.',
24
+ annotations: { readOnlyHint: true },
25
+
26
+ inputSchema: {
27
+ query: z.string().describe('What to look for')
28
+ },
29
+
30
+ async handler({ query }, extra) {
31
+ const term = `%${query}%`;
32
+
33
+ // Both conditions in ONE group. Content joins the query's top-level
34
+ // conditions with AND regardless of how each was added, so a `where()`
35
+ // followed by an `orWhere()` asks for a term in the title AND in the
36
+ // description — which matched nothing at all. Inside a group the operator
37
+ // is the one the group was opened with, so `orWhere` gives title OR
38
+ // description.
39
+ const pages = (
40
+ await Promise.all(
41
+ duxtCollections().map((name) =>
42
+ queryCollection(extra.event, name)
43
+ .select('path', 'title', 'description')
44
+ .orWhere((group) =>
45
+ group
46
+ .where('title', 'LIKE', term)
47
+ .where('description', 'LIKE', term)
48
+ )
49
+ .all()
50
+ )
51
+ )
52
+ ).flat();
53
+
54
+ if (!pages.length) {
55
+ return {
56
+ content: [{ type: 'text', text: `Nothing matches "${query}".` }]
57
+ };
58
+ }
59
+
60
+ return {
61
+ content: [
62
+ {
63
+ type: 'text',
64
+ text: pages
65
+ .map(
66
+ (page) =>
67
+ `- ${page.path} — ${page.title}${page.description ? `: ${page.description}` : ''}`
68
+ )
69
+ .join('\n')
70
+ }
71
+ ]
72
+ };
73
+ }
74
+ });
@@ -0,0 +1,86 @@
1
+ import { queryCollection } from '@nuxt/content/nitro';
2
+ import { duxtDefaults, mergeDuxtConfig } from '../../app/utils/duxt-config';
3
+ import { stripFrontmatter } from '../utils/duxt-server-text';
4
+
5
+ /**
6
+ * `…/guide/deploying.md` serves the page's Markdown source.
7
+ *
8
+ * What "View as Markdown" opens, and what the ChatGPT and Claude links hand
9
+ * over — a model given the rendered HTML has to undo a layout to find a
10
+ * heading, and one given this reads what the author wrote.
11
+ *
12
+ * A MIDDLEWARE, not a route. Nitro's router matches path segments, and `.md` is
13
+ * a suffix on the last one rather than a segment of its own, so there is no
14
+ * pattern that means "any path ending in .md". The middleware answers those and
15
+ * hands everything else straight on.
16
+ */
17
+ export default defineEventHandler(async (event) => {
18
+ const path = event.path?.split('?')[0];
19
+ if (!path?.endsWith('.md')) return;
20
+
21
+ const appConfig = useAppConfig() as { duxt?: Partial<DuxtConfig> };
22
+ const duxt = mergeDuxtConfig(appConfig.duxt, duxtDefaults);
23
+
24
+ const wanted = stripLocale(path.slice(0, -'.md'.length));
25
+
26
+ // Longest prefix first, exactly as the app resolves a route: `/app/v2` beats
27
+ // `/app` on `/app/v2/guide`.
28
+ const sources = [...(duxt.resolvedSources ?? [])].sort(
29
+ (a, b) => b.prefix.length - a.prefix.length
30
+ );
31
+
32
+ const source =
33
+ sources.find(
34
+ (entry) =>
35
+ !entry.prefix ||
36
+ wanted === entry.prefix ||
37
+ wanted.startsWith(`${entry.prefix}/`)
38
+ ) ?? sources[0];
39
+
40
+ if (!source) return;
41
+
42
+ const page = await queryCollection(
43
+ event,
44
+ source.collection as Parameters<typeof queryCollection>[1]
45
+ )
46
+ .path(wanted)
47
+ .select('title', 'rawbody')
48
+ .first();
49
+
50
+ // No page is not this middleware's error to raise: falling through lets the
51
+ // app answer with its own 404, which knows how to suggest a near miss.
52
+ if (!page) return;
53
+
54
+ const body = (page as { rawbody?: string }).rawbody;
55
+ if (!body) return;
56
+
57
+ setHeader(event, 'content-type', 'text/markdown; charset=utf-8');
58
+ return stripFrontmatter(body).trim();
59
+ });
60
+
61
+ /**
62
+ * The locale segment, off.
63
+ *
64
+ * The browser is on `/de-DE/guide/deploying`; the page is `/guide/deploying`.
65
+ * Same distinction `stripLocalePrefix` makes in the app, made again here
66
+ * because Nitro has no i18n composable to ask.
67
+ */
68
+ function stripLocale(path: string): string {
69
+ const first = path.split('/')[1];
70
+ if (!first) return path;
71
+
72
+ const locales = (
73
+ useRuntimeConfig().public as {
74
+ i18n?: { locales?: ({ code: string } | string)[] };
75
+ }
76
+ ).i18n?.locales;
77
+
78
+ const codes = (locales ?? []).map((locale) =>
79
+ typeof locale === 'string' ? locale : locale.code
80
+ );
81
+
82
+ if (!codes.includes(first)) return path;
83
+
84
+ const rest = path.slice(first.length + 1);
85
+ return rest.startsWith('/') ? rest : rest || '/';
86
+ }
@@ -0,0 +1,75 @@
1
+ import { queryCollection } from '@nuxt/content/nitro';
2
+ // Imported by path, not auto-import: app/ belongs to the Vue app, and Nitro
3
+ // does not see its utils. The defaults have to come from the same file the
4
+ // pages use, or the two descriptions drift apart.
5
+ import { duxtDefaults, mergeDuxtConfig } from '../../app/utils/duxt-config';
6
+ import {
7
+ resolveServerTexts,
8
+ stripFrontmatter
9
+ } from '../utils/duxt-server-text';
10
+
11
+ /**
12
+ * llms-full.txt — the whole documentation as one Markdown file.
13
+ *
14
+ * The companion to `llms.txt`, which is an index: a model that has read the
15
+ * index still has to fetch sixty pages, and a model given this one has read
16
+ * them. The convention (llmstxt.org) names both.
17
+ *
18
+ * The Markdown as written, not the rendered HTML — which is why the collection
19
+ * schema asks Content for `rawbody`. An MDC block reaches the reader as the
20
+ * component call it is, and a code fence is still a fence.
21
+ */
22
+ export default defineEventHandler(async (event) => {
23
+ const appConfig = useAppConfig() as { duxt?: Partial<DuxtConfig> };
24
+ // Resolved here because Nitro has no i18n: without it the description is
25
+ // printed as the translation key it is.
26
+ const duxt = resolveServerTexts(
27
+ mergeDuxtConfig(appConfig.duxt, duxtDefaults)
28
+ );
29
+
30
+ const collections = duxt.resolvedSources?.length
31
+ ? duxt.resolvedSources.map((source) => source.collection)
32
+ : ['docs'];
33
+
34
+ const pages = (
35
+ await Promise.all(
36
+ collections.map((name) =>
37
+ queryCollection(event, name as Parameters<typeof queryCollection>[1])
38
+ .select('path', 'title', 'description', 'rawbody')
39
+ .all()
40
+ )
41
+ )
42
+ ).flat();
43
+
44
+ const origin = getRequestURL(event).origin;
45
+
46
+ const lines = [
47
+ `# ${duxt.title}`,
48
+ '',
49
+ `> ${duxt.landing?.description ?? 'Documentation.'}`,
50
+ ''
51
+ ];
52
+
53
+ for (const page of pages
54
+ .filter((page) => page.path)
55
+ .sort((a, b) => a.path!.localeCompare(b.path!))) {
56
+ lines.push(
57
+ `---`,
58
+ '',
59
+ `# ${page.title ?? page.path}`,
60
+ '',
61
+ `Source: ${origin}${page.path}`,
62
+ ''
63
+ );
64
+
65
+ if (page.description) lines.push(`> ${page.description}`, '');
66
+
67
+ // A page whose collection was declared before `rawbody` was asked for has
68
+ // none. Its title and URL are still worth listing.
69
+ const body = (page as { rawbody?: string }).rawbody;
70
+ if (body) lines.push(stripFrontmatter(body).trim(), '');
71
+ }
72
+
73
+ setHeader(event, 'content-type', 'text/plain; charset=utf-8');
74
+ return lines.join('\n');
75
+ });