@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,440 @@
1
+ import { readFileSync } from 'node:fs';
2
+ import { join } from 'node:path';
3
+ import { fileURLToPath } from 'node:url';
4
+ import type { Nuxt } from '@nuxt/schema';
5
+ import { enableWriteAheadLog } from '../content-cache';
6
+ import type { DuxtBuildConfig } from '../duxt-app-config';
7
+ import { readDuxtBuildConfig } from '../duxt-app-config';
8
+ import {
9
+ duxtDefaultRequestSamples,
10
+ duxtRequestSamples
11
+ } from '../app/utils/request-samples';
12
+ import { duxtManifest, duxtSectionTypes } from '../sections-resolve';
13
+ import { readSectionReports } from '../section-reports';
14
+ import { resolveLatestRefs } from '../sources-git';
15
+
16
+ /**
17
+ * The build-time half of duxt's config, read from the site's `app.config.ts`.
18
+ *
19
+ * Two jobs, one source of truth.
20
+ *
21
+ * RESOLVED SOURCES. `duxt.sources` in app.config is what a consumer WRITES —
22
+ * folders, repositories, refs. What the theme reads is the resolved manifest:
23
+ * which collection serves which URL prefix. Content settles that in a pass of
24
+ * its own that the app never sees, so it is resolved here and written back into
25
+ * the app config under `resolvedSources`. Separate key on purpose — one name
26
+ * for two shapes is the next trap, not a convenience.
27
+ *
28
+ * LOCALES. i18n's own `locales` array MERGES across layers instead of
29
+ * replacing, so a consumer declaring `['de-DE']` adds nothing and removes
30
+ * nothing — the other six stay routed, indexed and announced in hreflang.
31
+ * Measured, not assumed. Removing is the one thing i18n cannot say, so it is
32
+ * the only thing duxt adds; `defaultLocale` merges as you would expect and
33
+ * stays i18n's own key.
34
+ */
35
+ export default function duxtConfig(_options: unknown, nuxt: Nuxt) {
36
+ // FIRST, and before anything else touches the file: this is the earliest of
37
+ // duxt's modules, so it is the only one that runs while Content's cache
38
+ // database is still closed — and the journal mode can only be changed then.
39
+ // See `enableWriteAheadLog`.
40
+ enableWriteAheadLog(nuxt);
41
+
42
+ const layerDir = fileURLToPath(new URL('..', import.meta.url));
43
+
44
+ const dirs = [
45
+ ...nuxt.options._layers.flatMap((entry) => [
46
+ entry.config.rootDir,
47
+ entry.config.srcDir
48
+ ]),
49
+ layerDir
50
+ ].filter(Boolean) as string[];
51
+
52
+ const config = readDuxtBuildConfig(dirs);
53
+
54
+ const sectionTypes = duxtSectionTypes(config?.sectionTypes);
55
+
56
+ // Read here, and the reports travel with the manifest into `appConfig` — the
57
+ // only route by which what an artefact said reaches the running server, and
58
+ // therefore the Checks panel. See `readSectionReports`.
59
+ const resolvedSources = readSectionReports(
60
+ duxtManifest(
61
+ resolveLatestRefs(config?.sources ?? [{ path: 'docs' }]),
62
+ config?.sourceOptions ?? {},
63
+ sectionTypes
64
+ ),
65
+ sectionTypes
66
+ );
67
+
68
+ // Written into `appConfig`, which the generated template merges LAST — behind
69
+ // every app.config.ts file. That is the right way round: a value the build
70
+ // computes must not overwrite one a human wrote.
71
+ // Cast because the appConfig type is GENERATED FROM THIS ASSIGNMENT: Nuxt
72
+ // infers it from whatever was written last time, so the module cannot satisfy
73
+ // a type it is itself the only source of.
74
+ nuxt.options.appConfig.duxt = {
75
+ ...nuxt.options.appConfig.duxt,
76
+ ...layerIdentity(layerDir),
77
+ resolvedSources
78
+ } as typeof nuxt.options.appConfig.duxt;
79
+
80
+ writeGrammars(nuxt, config);
81
+ checkSourceLocales(nuxt, config, resolvedSources);
82
+ restrictLocales(nuxt, config?.locales);
83
+ shareSiteUrl(nuxt);
84
+ nameMcpServer(nuxt, config?.title);
85
+ excludeOldVersionsFromSitemap(nuxt, resolvedSources);
86
+ }
87
+
88
+ /**
89
+ * The two ways a translated source can be configured into silence.
90
+ *
91
+ * NEITHER is caught by anything else. `content.config.ts` has no access to the
92
+ * Nuxt config — it is loaded by c12 in Content's own pass — so it resolves the
93
+ * default locale from `sourceOptions.defaultLocale` alone, and if that
94
+ * disagrees with `i18n.defaultLocale` the collections are named for one
95
+ * language while the theme queries for another. The result is not an error but
96
+ * an empty page, which is the failure mode this layer exists to stop.
97
+ *
98
+ * So the value is NOT injected from i18n here — injecting it would fix the
99
+ * manifest and leave `content.config.ts` computing the other answer, which is
100
+ * the same bug one layer deeper. It is checked instead.
101
+ */
102
+ function checkSourceLocales(
103
+ nuxt: Nuxt,
104
+ config:
105
+ | { locales?: string[]; sourceOptions?: { defaultLocale?: string } }
106
+ | undefined,
107
+ resolved: ReturnType<typeof duxtManifest>
108
+ ) {
109
+ const translated = resolved.filter((source) => source.locale);
110
+ if (!translated.length) return;
111
+
112
+ const defaultLocale = nuxt.options.i18n?.defaultLocale;
113
+ const assumed = resolved.find((source) => source.isDefaultLocale)?.locale;
114
+
115
+ if (defaultLocale && assumed && assumed !== defaultLocale) {
116
+ throw new Error(
117
+ `duxt: sources treat "${assumed}" as the untranslated original, but ` +
118
+ `i18n.defaultLocale is "${defaultLocale}". Content declares the ` +
119
+ 'collections without access to the Nuxt config, so the two must agree: ' +
120
+ `set duxt.sourceOptions.defaultLocale to "${defaultLocale}".`
121
+ );
122
+ }
123
+
124
+ // A translation nobody can reach is a folder parsed, stored and served to no
125
+ // one — worth a message rather than a silently larger build.
126
+ const served = config?.locales;
127
+ if (!served?.length) return;
128
+
129
+ const stranded = [
130
+ ...new Set(
131
+ translated
132
+ .filter((source) => source.locale && !served.includes(source.locale))
133
+ .map((source) => source.locale!)
134
+ )
135
+ ];
136
+
137
+ if (stranded.length) {
138
+ console.warn(
139
+ `[duxt] sources are translated into ${stranded.join(', ')}, which ` +
140
+ `duxt.locales does not serve (${served.join(', ')}). Those ` +
141
+ 'collections are built and never read.'
142
+ );
143
+ }
144
+ }
145
+
146
+ /**
147
+ * The layer's own version and repository, for the footer line.
148
+ *
149
+ * Read out of the layer's `package.json` rather than written anywhere: the
150
+ * version is bumped by release-please, and a second copy of it is a copy that
151
+ * goes stale on the first release. Both are absent rather than guessed if the
152
+ * file cannot be read — the footer then draws nothing.
153
+ */
154
+ /**
155
+ * The grammars the RUNTIME highlighter loads, written from the configured
156
+ * samples.
157
+ *
158
+ * The samples beside the try-it client are coloured in the browser, because the
159
+ * request changes as the reader types and the server cannot re-render it. So
160
+ * unlike a Markdown fence — whose grammar is a build-time cost and never a
161
+ * client byte, see `highlight-langs.ts` — every language here IS bytes a reader
162
+ * downloads: python is 9 KB gzip, php 28 KB.
163
+ *
164
+ * WRITTEN, NOT LOOKED UP. Shiki's own `shiki/langs` is a lazy map of all 242,
165
+ * which would let any language load on demand for 3 KB — and make every
166
+ * consumer's build emit 242 grammar chunks. Generating the map from the resolved
167
+ * list instead means a site gets exactly the languages its samples name, and the
168
+ * `import()` calls are literal, so Vite can see them.
169
+ *
170
+ * The three at the bottom are not samples: the package-manager block, the
171
+ * response body and the example bodies are highlighted at runtime too, and they
172
+ * are the same three whatever a site configures.
173
+ */
174
+ function writeGrammars(nuxt: Nuxt, config?: DuxtBuildConfig): void {
175
+ const shipped = new Map(
176
+ duxtRequestSamples.map((sample) => [sample.id, sample.language])
177
+ );
178
+
179
+ const wanted = config?.requestSamples ?? duxtDefaultRequestSamples;
180
+
181
+ const languages = new Set([
182
+ 'bash',
183
+ 'json',
184
+ 'typescript',
185
+ ...(config?.sampleLanguages ?? [])
186
+ ]);
187
+
188
+ for (const entry of wanted) {
189
+ const language =
190
+ typeof entry === 'string' ? shipped.get(entry) : entry?.language;
191
+
192
+ if (language) languages.add(language);
193
+ }
194
+
195
+ const loaders = [...languages]
196
+ .sort()
197
+ .map(
198
+ (language) =>
199
+ ` ${JSON.stringify(language)}: () => import('shiki/langs/${language}.mjs')`
200
+ )
201
+ .join(',\n');
202
+
203
+ // `nuxt.options.build.templates` rather than `addTemplate`: `@nuxt/kit` is not
204
+ // a dependency of this layer and none of its five modules imports one, which
205
+ // keeps the layer's install free of a package that has to track `nuxt`'s own
206
+ // version. This is what the helper does anyway.
207
+ nuxt.options.build.templates.push(
208
+ {
209
+ filename: 'duxt-grammars.mjs',
210
+ getContents: () => `export const grammars = {\n${loaders}\n};\n`
211
+ },
212
+ {
213
+ filename: 'types/duxt-grammars.d.ts',
214
+ write: true,
215
+ getContents: () =>
216
+ [
217
+ "declare module '#build/duxt-grammars.mjs' {",
218
+ ' export const grammars: Record<string, () => Promise<unknown>>;',
219
+ '}',
220
+ ''
221
+ ].join('\n')
222
+ }
223
+ );
224
+
225
+ nuxt.hook('prepare:types', ({ references }) => {
226
+ references.push({
227
+ path: join(nuxt.options.buildDir, 'types/duxt-grammars.d.ts')
228
+ });
229
+ });
230
+ }
231
+
232
+ function layerIdentity(layerDir: string) {
233
+ try {
234
+ const pkg = JSON.parse(
235
+ readFileSync(new URL('package.json', `file://${layerDir}`), 'utf8')
236
+ ) as { version?: string; repository?: { url?: string } };
237
+
238
+ return {
239
+ layerVersion: pkg.version,
240
+ layerRepository: pkg.repository?.url
241
+ ?.replace(/^git\+/, '')
242
+ .replace(/\.git$/, '')
243
+ };
244
+ } catch {
245
+ return {};
246
+ }
247
+ }
248
+
249
+ /**
250
+ * One origin, stated once.
251
+ *
252
+ * `i18n.baseUrl` is where a Nuxt site already has to say what domain it is
253
+ * served from — hreflang is not valid relative — and robots, the sitemap and
254
+ * the OG images each want the same answer under a different key. Copying it
255
+ * here means a consumer sets one value; an explicit `site.url` still wins,
256
+ * because a human wrote it.
257
+ */
258
+ function shareSiteUrl(nuxt: Nuxt) {
259
+ const baseUrl = (nuxt.options as { i18n?: { baseUrl?: string } }).i18n
260
+ ?.baseUrl;
261
+
262
+ if (!baseUrl || typeof baseUrl !== 'string') return;
263
+
264
+ const site = (nuxt.options as { site?: { url?: string } }).site ?? {};
265
+ if (site.url) return;
266
+
267
+ (nuxt.options as { site?: { url?: string } }).site = {
268
+ ...site,
269
+ url: baseUrl
270
+ };
271
+ }
272
+
273
+ /**
274
+ * The MCP server's name, from the site's own title.
275
+ *
276
+ * `mcp.name` is a nuxt.config key, not an app.config one, so it is the single
277
+ * duxt-facing option a consumer cannot set beside the others. Left as a literal
278
+ * it published duxt's own name from every downstream site. Derived here
279
+ * instead: the title a consumer already writes in `app.config.ts` names the
280
+ * server too, and anyone wanting a different one still writes `mcp: { name }`
281
+ * in `nuxt.config.ts`, which defu keeps ahead of this.
282
+ *
283
+ * A record is resolved against `i18n.defaultLocale`, because the server has one
284
+ * name and no request to read a language from. A consumer who wrote an i18n KEY
285
+ * as their title gets that key — the build has no translator, which is why a
286
+ * literal or a record is the documented form.
287
+ *
288
+ * The module's own default is the empty string, so this must always answer.
289
+ */
290
+ function nameMcpServer(
291
+ nuxt: Nuxt,
292
+ title: string | Record<string, string> | undefined
293
+ ) {
294
+ const options = nuxt.options as {
295
+ mcp?: { name?: string };
296
+ i18n?: { defaultLocale?: string };
297
+ };
298
+ if (!options.mcp || options.mcp.name) return;
299
+
300
+ const locale = options.i18n?.defaultLocale;
301
+ const name =
302
+ typeof title === 'string'
303
+ ? title
304
+ : title
305
+ ? ((locale && title[locale]) ?? Object.values(title)[0])
306
+ : undefined;
307
+
308
+ options.mcp.name = name ? `${name} documentation` : 'Documentation';
309
+ }
310
+
311
+ /**
312
+ * Keep the versions the page tells a crawler to ignore out of the sitemap.
313
+ *
314
+ * A non-default version carries `noindex` and a canonical pointing at the
315
+ * current one; an `eol` version is gone whether or not it is the default.
316
+ * Listing either in the sitemap asks a crawler to fetch exactly what the page
317
+ * then tells it to drop — the two halves have to agree, so both are derived
318
+ * from the same manifest.
319
+ *
320
+ * Four patterns per prefix because the locale segment sits in front of it:
321
+ * `/workflows/v0.7.0` and `/de-DE/workflows/v0.7.0` are the same page.
322
+ */
323
+ function excludeOldVersionsFromSitemap(
324
+ nuxt: Nuxt,
325
+ sources: ReturnType<typeof duxtManifest>
326
+ ) {
327
+ const hidden = sources.filter(
328
+ (source) => source.prefix && (!source.isDefault || source.status === 'eol')
329
+ );
330
+
331
+ if (!hidden.length) return;
332
+
333
+ const options = nuxt.options as {
334
+ sitemap?: { exclude?: string[] };
335
+ };
336
+
337
+ const exclude = options.sitemap?.exclude ?? [];
338
+
339
+ for (const source of hidden) {
340
+ exclude.push(
341
+ source.prefix,
342
+ `${source.prefix}/**`,
343
+ `/*${source.prefix}`,
344
+ `/*${source.prefix}/**`
345
+ );
346
+ }
347
+
348
+ options.sitemap = { ...options.sitemap, exclude: [...new Set(exclude)] };
349
+ }
350
+
351
+ function restrictLocales(nuxt: Nuxt, wanted: string[] | undefined) {
352
+ if (!wanted?.length) return;
353
+
354
+ const configs = nuxt.options._layers
355
+ .map((entry) => (entry.config as { i18n?: { locales?: unknown[] } }).i18n)
356
+ .filter((config): config is { locales?: unknown[] } =>
357
+ Boolean(config?.locales)
358
+ );
359
+
360
+ const codeOf = (locale: unknown) =>
361
+ typeof locale === 'string' ? locale : (locale as { code: string }).code;
362
+
363
+ const available = [
364
+ ...new Set(configs.flatMap((config) => config.locales!.map(codeOf)))
365
+ ];
366
+
367
+ const unknown = wanted.filter((code) => !available.includes(code));
368
+ if (unknown.length) {
369
+ // Rejected rather than silently dropped: a typo in a locale code would
370
+ // otherwise present as a language simply missing from the site, with
371
+ // nothing in the build connecting the two.
372
+ throw new Error(
373
+ `duxt: unknown locale${unknown.length > 1 ? 's' : ''} in app.config ` +
374
+ `duxt.locales: ${unknown.join(', ')}. Available: ${available.join(', ')}. ` +
375
+ 'Declare it in i18n.locales to add a locale the layer does not ship.'
376
+ );
377
+ }
378
+
379
+ /**
380
+ * Filtered PER LAYER, not on the merged config: i18n collects `i18n.locales`
381
+ * from every layer itself, so rewriting `nuxt.options.i18n` changes a copy
382
+ * nothing reads. Found the hard way — the merged list said two locales and
383
+ * the built site still served seven.
384
+ */
385
+ for (const config of configs) {
386
+ config.locales = config.locales!.filter((locale) =>
387
+ wanted.includes(codeOf(locale))
388
+ );
389
+ }
390
+
391
+ const defaultLocale = nuxt.options.i18n?.defaultLocale;
392
+ if (defaultLocale && !wanted.includes(defaultLocale)) {
393
+ throw new Error(
394
+ `duxt: i18n.defaultLocale is "${defaultLocale}", which app.config ` +
395
+ `duxt.locales does not list (${wanted.join(', ')}). ` +
396
+ 'Set i18n.defaultLocale to one of them.'
397
+ );
398
+ }
399
+
400
+ /**
401
+ * The same question, asked of the OTHER fallback — the one that decides where
402
+ * a visitor landing on `/` is sent.
403
+ *
404
+ * `detectBrowserLanguage.fallbackLocale` is nested, and defu merges nested
405
+ * objects key by key: a consumer setting `i18n: { defaultLocale: 'de-DE' }`
406
+ * does NOT displace the layer's `en-GB` here. Narrow `duxt.locales` to
407
+ * exclude it and the site stops serving that language while still redirecting
408
+ * root visitors to it — nothing throws, and the page is empty. Exactly the
409
+ * class of silent failure the check above exists to stop, one key over.
410
+ */
411
+ const browserFallback = (
412
+ nuxt.options.i18n as { detectBrowserLanguage?: { fallbackLocale?: string } }
413
+ )?.detectBrowserLanguage?.fallbackLocale;
414
+
415
+ if (browserFallback && !wanted.includes(browserFallback)) {
416
+ throw new Error(
417
+ `duxt: i18n.detectBrowserLanguage.fallbackLocale is "${browserFallback}", ` +
418
+ `which app.config duxt.locales does not list (${wanted.join(', ')}). ` +
419
+ 'Set it to one of them, or widen duxt.locales.'
420
+ );
421
+ }
422
+
423
+ // The flags were derived from the full list at config time. Prune them too,
424
+ // so a site serving two languages does not inline seven it never draws.
425
+ const keep = new Set(
426
+ wanted.map((code) => `flag:${code.split('-')[1]?.toLowerCase()}-4x3`)
427
+ );
428
+
429
+ for (const entry of nuxt.options._layers) {
430
+ const icon = (
431
+ entry.config as { icon?: { clientBundle?: { icons?: string[] } } }
432
+ ).icon;
433
+
434
+ if (icon?.clientBundle?.icons) {
435
+ icon.clientBundle.icons = icon.clientBundle.icons.filter(
436
+ (name) => !name.startsWith('flag:') || keep.has(name)
437
+ );
438
+ }
439
+ }
440
+ }
@@ -0,0 +1,126 @@
1
+ import { existsSync } from 'node:fs';
2
+ import { join } from 'node:path';
3
+ import { fileURLToPath } from 'node:url';
4
+ import type { Nuxt } from '@nuxt/schema';
5
+
6
+ /**
7
+ * A window into the layer that has been hardest to debug.
8
+ *
9
+ * `sources` is a compact list a consumer writes; what the site actually serves
10
+ * is a set of collections, prefixes, redirects, message catalogues and a
11
+ * download cache that the build computed from it, and until now the only way to
12
+ * see any of that was to read the code that produces it. Every version bug this
13
+ * repo had would have been obvious from one of these panels.
14
+ *
15
+ * Registered through the `devtools:customTabs` hook rather than
16
+ * `@nuxt/devtools-kit`'s `addCustomTab`, which is the same push into the same
17
+ * array — one fewer dependency in a published layer for no loss.
18
+ *
19
+ * Dev only: the panels expose the resolved configuration, the file system paths
20
+ * behind it and a button that deletes a cache directory. None of that is
21
+ * anybody's business in production, and returning here means the route is never
22
+ * registered rather than registered and guarded.
23
+ */
24
+ export default function duxtDevtools(_options: unknown, nuxt: Nuxt) {
25
+ if (!nuxt.options.dev) return;
26
+
27
+ const layerDir = fileURLToPath(new URL('..', import.meta.url));
28
+
29
+ /**
30
+ * Paths only — no resolved data.
31
+ *
32
+ * The first version put the whole resolved manifest in here, and it went
33
+ * stale the moment anyone edited `app.config.ts`: a virtual module is
34
+ * generated once, so the tab kept describing the previous build while the
35
+ * site served the new one. What a request handler genuinely cannot work out
36
+ * for itself is where things are on disk, so that is all this carries; the
37
+ * panels read everything else from the running app config.
38
+ */
39
+ const context = {
40
+ rootDir: nuxt.options.rootDir,
41
+ layerDir,
42
+ dataDir: join(nuxt.options.rootDir, '.data/content'),
43
+ appConfigFile: appConfigOf(nuxt),
44
+ locales: localeCodes(nuxt),
45
+ defaultLocale: (nuxt.options as { i18n?: { defaultLocale?: string } }).i18n
46
+ ?.defaultLocale,
47
+ // The Paths panel picks a collection per locale, through the same chain the
48
+ // theme walks. vue-i18n's own `fallbackLocale` lives in `i18n.config.ts`,
49
+ // which is a runtime module Nuxt does not evaluate here — and the default
50
+ // locale is where that chain ends regardless, because the untranslated
51
+ // original is the collection a page falls back to when nothing else has it.
52
+ fallbackLocale: (nuxt.options as { i18n?: { defaultLocale?: string } }).i18n
53
+ ?.defaultLocale
54
+ };
55
+
56
+ // The handler lives OUTSIDE `server/routes/`, so this is the only thing that
57
+ // registers it. Nitro scans a layer's route directory in every build, which
58
+ // made the early return above a comfort rather than a guard: the tab was in
59
+ // the production bundle until the file moved.
60
+ nuxt.hook('nitro:config', (nitro) => {
61
+ nitro.virtual ||= {};
62
+ nitro.virtual['#duxt-devtools'] =
63
+ `export const devtools = ${JSON.stringify(context)};`;
64
+
65
+ const handler = fileURLToPath(
66
+ new URL('../server/devtools/handler.ts', import.meta.url)
67
+ );
68
+
69
+ nitro.handlers ||= [];
70
+
71
+ // Both spellings: the bare path is the tab's own URL, and `/**` carries the
72
+ // panel. Matching only the wildcard leaves the tab itself a 404.
73
+ for (const route of ['/_duxt/devtools', '/_duxt/devtools/**']) {
74
+ nitro.handlers.push({ route, handler });
75
+ }
76
+ });
77
+
78
+ nuxt.hook(
79
+ 'devtools:customTabs' as never,
80
+ ((tabs: unknown[]) => {
81
+ tabs.push({
82
+ name: 'duxt',
83
+ title: 'duxt',
84
+ icon: 'lucide:book-open-text',
85
+ view: {
86
+ type: 'iframe',
87
+ src: '/_duxt/devtools'
88
+ }
89
+ });
90
+ }) as never
91
+ );
92
+ }
93
+
94
+ /** The consumer's `app.config.ts`, so a panel can say where to go and edit it. */
95
+ function appConfigOf(nuxt: Nuxt): string | undefined {
96
+ const dirs = nuxt.options._layers.flatMap((entry) => [
97
+ entry.config.rootDir,
98
+ entry.config.srcDir
99
+ ]);
100
+
101
+ for (const dir of dirs) {
102
+ if (!dir) continue;
103
+
104
+ const file = [
105
+ join(dir, 'app', 'app.config.ts'),
106
+ join(dir, 'app.config.ts')
107
+ ].find((candidate) => existsSync(candidate));
108
+
109
+ if (file) return file;
110
+ }
111
+
112
+ return undefined;
113
+ }
114
+
115
+ /** The locale segments a URL may carry, for the path debugger to strip. */
116
+ function localeCodes(nuxt: Nuxt): string[] {
117
+ const locales = (
118
+ nuxt.options as {
119
+ i18n?: { locales?: (string | { code: string })[] };
120
+ }
121
+ ).i18n?.locales;
122
+
123
+ return (locales ?? []).map((locale) =>
124
+ typeof locale === 'string' ? locale : locale.code
125
+ );
126
+ }