@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.
- package/LICENSE +21 -0
- package/README.md +151 -0
- package/app/app.config.ts +13 -0
- package/app/app.vue +122 -0
- package/app/assets/css/duxt.css +555 -0
- package/app/assets/css/typeset.css +498 -0
- package/app/components/DuxtBrand.vue +53 -0
- package/app/components/DuxtBreadcrumb.vue +37 -0
- package/app/components/DuxtCodeBlock.vue +143 -0
- package/app/components/DuxtCopyPage.vue +117 -0
- package/app/components/DuxtFooter.vue +86 -0
- package/app/components/DuxtHeader.vue +496 -0
- package/app/components/DuxtJsonEditor.vue +359 -0
- package/app/components/DuxtLandingProse.vue +90 -0
- package/app/components/DuxtLandingShowcase.vue +295 -0
- package/app/components/DuxtLiveWindow.vue +343 -0
- package/app/components/DuxtLocale.vue +86 -0
- package/app/components/DuxtMetaList.vue +76 -0
- package/app/components/DuxtNavigation.vue +94 -0
- package/app/components/DuxtNavigationLink.vue +41 -0
- package/app/components/DuxtOpenApiCallbacks.vue +92 -0
- package/app/components/DuxtOpenApiClient.vue +1339 -0
- package/app/components/DuxtOpenApiMedia.vue +108 -0
- package/app/components/DuxtOpenApiMethod.vue +59 -0
- package/app/components/DuxtOpenApiParameters.vue +114 -0
- package/app/components/DuxtOpenApiResponses.vue +109 -0
- package/app/components/DuxtOpenApiSchema.vue +365 -0
- package/app/components/DuxtOpenApiSecurity.vue +115 -0
- package/app/components/DuxtPageFeedback.vue +56 -0
- package/app/components/DuxtPageInfo.vue +194 -0
- package/app/components/DuxtPageNav.vue +93 -0
- package/app/components/DuxtPreviewSkeleton.vue +262 -0
- package/app/components/DuxtProgress.vue +45 -0
- package/app/components/DuxtSearch.vue +271 -0
- package/app/components/DuxtSections.vue +65 -0
- package/app/components/DuxtShortcuts.vue +46 -0
- package/app/components/DuxtSkipLink.vue +34 -0
- package/app/components/DuxtToc.vue +86 -0
- package/app/components/DuxtTranslationBanner.vue +64 -0
- package/app/components/DuxtVersion.vue +168 -0
- package/app/components/DuxtVersionBanner.vue +109 -0
- package/app/components/OgImage/Duxt.satori.vue +82 -0
- package/app/components/content/Accordion.vue +37 -0
- package/app/components/content/AccordionItem.vue +46 -0
- package/app/components/content/Callout.vue +58 -0
- package/app/components/content/ChangelogGroup.vue +91 -0
- package/app/components/content/ChangelogReleases.vue +520 -0
- package/app/components/content/CodeGroup.vue +183 -0
- package/app/components/content/DevtoolsPanel.vue +107 -0
- package/app/components/content/Field.vue +50 -0
- package/app/components/content/FieldGroup.vue +8 -0
- package/app/components/content/FileTree.vue +92 -0
- package/app/components/content/Mermaid.vue +70 -0
- package/app/components/content/OpenApiOperation.vue +185 -0
- package/app/components/content/OpenApiOperations.vue +72 -0
- package/app/components/content/OpenApiOverview.vue +302 -0
- package/app/components/content/PackageManagers.vue +187 -0
- package/app/components/content/PageCards.vue +48 -0
- package/app/components/content/Partial.vue +65 -0
- package/app/components/content/Preview.vue +173 -0
- package/app/components/content/ProseA.vue +55 -0
- package/app/components/content/ProseH2.vue +53 -0
- package/app/components/content/ProseH3.vue +53 -0
- package/app/components/content/ProseH4.vue +53 -0
- package/app/components/content/ProseImg.vue +87 -0
- package/app/components/content/ProsePre.vue +58 -0
- package/app/components/content/ProseTable.vue +11 -0
- package/app/components/content/Since.vue +21 -0
- package/app/components/content/Steps.vue +20 -0
- package/app/components/content/Tab.vue +23 -0
- package/app/components/content/TabGroup.vue +58 -0
- package/app/components/ui/alert/Alert.vue +21 -0
- package/app/components/ui/alert/AlertDescription.vue +22 -0
- package/app/components/ui/alert/AlertTitle.vue +22 -0
- package/app/components/ui/alert/index.ts +24 -0
- package/app/components/ui/badge/Badge.vue +28 -0
- package/app/components/ui/badge/index.ts +52 -0
- package/app/components/ui/breadcrumb/Breadcrumb.vue +13 -0
- package/app/components/ui/breadcrumb/BreadcrumbEllipsis.vue +22 -0
- package/app/components/ui/breadcrumb/BreadcrumbItem.vue +17 -0
- package/app/components/ui/breadcrumb/BreadcrumbLink.vue +24 -0
- package/app/components/ui/breadcrumb/BreadcrumbList.vue +22 -0
- package/app/components/ui/breadcrumb/BreadcrumbPage.vue +20 -0
- package/app/components/ui/breadcrumb/BreadcrumbSeparator.vue +21 -0
- package/app/components/ui/breadcrumb/index.ts +7 -0
- package/app/components/ui/button/Button.vue +31 -0
- package/app/components/ui/button/index.ts +39 -0
- package/app/components/ui/card/Card.vue +22 -0
- package/app/components/ui/card/CardAction.vue +22 -0
- package/app/components/ui/card/CardContent.vue +14 -0
- package/app/components/ui/card/CardDescription.vue +17 -0
- package/app/components/ui/card/CardFooter.vue +17 -0
- package/app/components/ui/card/CardHeader.vue +22 -0
- package/app/components/ui/card/CardTitle.vue +17 -0
- package/app/components/ui/card/index.ts +7 -0
- package/app/components/ui/collapsible/Collapsible.vue +19 -0
- package/app/components/ui/collapsible/CollapsibleContent.vue +12 -0
- package/app/components/ui/collapsible/CollapsibleTrigger.vue +12 -0
- package/app/components/ui/collapsible/index.ts +3 -0
- package/app/components/ui/command/Command.vue +98 -0
- package/app/components/ui/command/CommandDialog.vue +42 -0
- package/app/components/ui/command/CommandEmpty.vue +31 -0
- package/app/components/ui/command/CommandGroup.vue +52 -0
- package/app/components/ui/command/CommandInput.vue +45 -0
- package/app/components/ui/command/CommandItem.vue +85 -0
- package/app/components/ui/command/CommandList.vue +32 -0
- package/app/components/ui/command/CommandSeparator.vue +23 -0
- package/app/components/ui/command/CommandShortcut.vue +19 -0
- package/app/components/ui/command/index.ts +29 -0
- package/app/components/ui/dialog/Dialog.vue +15 -0
- package/app/components/ui/dialog/DialogClose.vue +12 -0
- package/app/components/ui/dialog/DialogContent.vue +61 -0
- package/app/components/ui/dialog/DialogDescription.vue +25 -0
- package/app/components/ui/dialog/DialogFooter.vue +30 -0
- package/app/components/ui/dialog/DialogHeader.vue +17 -0
- package/app/components/ui/dialog/DialogOverlay.vue +28 -0
- package/app/components/ui/dialog/DialogScrollContent.vue +65 -0
- package/app/components/ui/dialog/DialogTitle.vue +25 -0
- package/app/components/ui/dialog/DialogTrigger.vue +12 -0
- package/app/components/ui/dialog/index.ts +10 -0
- package/app/components/ui/dropdown-menu/DropdownMenu.vue +19 -0
- package/app/components/ui/dropdown-menu/DropdownMenuCheckboxItem.vue +47 -0
- package/app/components/ui/dropdown-menu/DropdownMenuContent.vue +47 -0
- package/app/components/ui/dropdown-menu/DropdownMenuGroup.vue +12 -0
- package/app/components/ui/dropdown-menu/DropdownMenuItem.vue +41 -0
- package/app/components/ui/dropdown-menu/DropdownMenuLabel.vue +27 -0
- package/app/components/ui/dropdown-menu/DropdownMenuRadioGroup.vue +21 -0
- package/app/components/ui/dropdown-menu/DropdownMenuRadioItem.vue +48 -0
- package/app/components/ui/dropdown-menu/DropdownMenuSeparator.vue +23 -0
- package/app/components/ui/dropdown-menu/DropdownMenuShortcut.vue +19 -0
- package/app/components/ui/dropdown-menu/DropdownMenuSub.vue +19 -0
- package/app/components/ui/dropdown-menu/DropdownMenuSubContent.vue +34 -0
- package/app/components/ui/dropdown-menu/DropdownMenuSubTrigger.vue +34 -0
- package/app/components/ui/dropdown-menu/DropdownMenuTrigger.vue +17 -0
- package/app/components/ui/dropdown-menu/index.ts +16 -0
- package/app/components/ui/input/Input.vue +35 -0
- package/app/components/ui/input/index.ts +1 -0
- package/app/components/ui/navigation-menu/NavigationMenu.vue +42 -0
- package/app/components/ui/navigation-menu/NavigationMenuContent.vue +35 -0
- package/app/components/ui/navigation-menu/NavigationMenuIndicator.vue +32 -0
- package/app/components/ui/navigation-menu/NavigationMenuItem.vue +23 -0
- package/app/components/ui/navigation-menu/NavigationMenuLink.vue +30 -0
- package/app/components/ui/navigation-menu/NavigationMenuList.vue +30 -0
- package/app/components/ui/navigation-menu/NavigationMenuTrigger.vue +31 -0
- package/app/components/ui/navigation-menu/NavigationMenuViewport.vue +30 -0
- package/app/components/ui/navigation-menu/index.ts +14 -0
- package/app/components/ui/scroll-area/ScrollArea.vue +31 -0
- package/app/components/ui/scroll-area/ScrollBar.vue +38 -0
- package/app/components/ui/scroll-area/index.ts +2 -0
- package/app/components/ui/select/Select.vue +15 -0
- package/app/components/ui/select/SelectContent.vue +59 -0
- package/app/components/ui/select/SelectGroup.vue +19 -0
- package/app/components/ui/select/SelectItem.vue +42 -0
- package/app/components/ui/select/SelectItemText.vue +12 -0
- package/app/components/ui/select/SelectLabel.vue +16 -0
- package/app/components/ui/select/SelectScrollDownButton.vue +28 -0
- package/app/components/ui/select/SelectScrollUpButton.vue +28 -0
- package/app/components/ui/select/SelectSeparator.vue +20 -0
- package/app/components/ui/select/SelectTrigger.vue +32 -0
- package/app/components/ui/select/SelectValue.vue +12 -0
- package/app/components/ui/select/index.ts +11 -0
- package/app/components/ui/separator/Separator.vue +30 -0
- package/app/components/ui/separator/index.ts +1 -0
- package/app/components/ui/sheet/Sheet.vue +15 -0
- package/app/components/ui/sheet/SheetClose.vue +12 -0
- package/app/components/ui/sheet/SheetContent.vue +68 -0
- package/app/components/ui/sheet/SheetDescription.vue +23 -0
- package/app/components/ui/sheet/SheetFooter.vue +15 -0
- package/app/components/ui/sheet/SheetHeader.vue +15 -0
- package/app/components/ui/sheet/SheetOverlay.vue +28 -0
- package/app/components/ui/sheet/SheetTitle.vue +23 -0
- package/app/components/ui/sheet/SheetTrigger.vue +12 -0
- package/app/components/ui/sheet/index.ts +8 -0
- package/app/components/ui/sidebar/Sidebar.vue +110 -0
- package/app/components/ui/sidebar/SidebarContent.vue +23 -0
- package/app/components/ui/sidebar/SidebarFooter.vue +18 -0
- package/app/components/ui/sidebar/SidebarGroup.vue +18 -0
- package/app/components/ui/sidebar/SidebarGroupAction.vue +31 -0
- package/app/components/ui/sidebar/SidebarGroupContent.vue +18 -0
- package/app/components/ui/sidebar/SidebarGroupLabel.vue +30 -0
- package/app/components/ui/sidebar/SidebarHeader.vue +18 -0
- package/app/components/ui/sidebar/SidebarInput.vue +19 -0
- package/app/components/ui/sidebar/SidebarInset.vue +23 -0
- package/app/components/ui/sidebar/SidebarMenu.vue +18 -0
- package/app/components/ui/sidebar/SidebarMenuAction.vue +42 -0
- package/app/components/ui/sidebar/SidebarMenuBadge.vue +28 -0
- package/app/components/ui/sidebar/SidebarMenuButton.vue +60 -0
- package/app/components/ui/sidebar/SidebarMenuButtonChild.vue +36 -0
- package/app/components/ui/sidebar/SidebarMenuItem.vue +18 -0
- package/app/components/ui/sidebar/SidebarMenuSkeleton.vue +35 -0
- package/app/components/ui/sidebar/SidebarMenuSub.vue +24 -0
- package/app/components/ui/sidebar/SidebarMenuSubButton.vue +43 -0
- package/app/components/ui/sidebar/SidebarMenuSubItem.vue +18 -0
- package/app/components/ui/sidebar/SidebarProvider.vue +109 -0
- package/app/components/ui/sidebar/SidebarRail.vue +35 -0
- package/app/components/ui/sidebar/SidebarSeparator.vue +19 -0
- package/app/components/ui/sidebar/SidebarTrigger.vue +26 -0
- package/app/components/ui/sidebar/index.ts +62 -0
- package/app/components/ui/sidebar/utils.ts +19 -0
- package/app/components/ui/skeleton/Skeleton.vue +17 -0
- package/app/components/ui/skeleton/index.ts +1 -0
- package/app/components/ui/sonner/Sonner.vue +67 -0
- package/app/components/ui/sonner/index.ts +1 -0
- package/app/components/ui/tabs/Tabs.vue +26 -0
- package/app/components/ui/tabs/TabsContent.vue +23 -0
- package/app/components/ui/tabs/TabsList.vue +28 -0
- package/app/components/ui/tabs/TabsTrigger.vue +30 -0
- package/app/components/ui/tabs/index.ts +4 -0
- package/app/components/ui/textarea/Textarea.vue +32 -0
- package/app/components/ui/textarea/index.ts +1 -0
- package/app/components/ui/tooltip/Tooltip.vue +15 -0
- package/app/components/ui/tooltip/TooltipContent.vue +49 -0
- package/app/components/ui/tooltip/TooltipProvider.vue +14 -0
- package/app/components/ui/tooltip/TooltipTrigger.vue +12 -0
- package/app/components/ui/tooltip/index.ts +4 -0
- package/app/composables/useActiveHeading.ts +90 -0
- package/app/composables/useDuxtAnimatedHeight.ts +155 -0
- package/app/composables/useDuxtBreadcrumb.ts +60 -0
- package/app/composables/useDuxtChoice.ts +28 -0
- package/app/composables/useDuxtCollection.ts +89 -0
- package/app/composables/useDuxtConfig.ts +47 -0
- package/app/composables/useDuxtNavigation.ts +87 -0
- package/app/composables/useDuxtPageFocus.ts +30 -0
- package/app/composables/useDuxtPath.ts +42 -0
- package/app/composables/useDuxtSearch.ts +208 -0
- package/app/composables/useDuxtSection.ts +44 -0
- package/app/composables/useDuxtSectionRow.ts +49 -0
- package/app/composables/useDuxtShortcuts.ts +58 -0
- package/app/composables/useDuxtSiteUrl.ts +27 -0
- package/app/composables/useDuxtToast.ts +25 -0
- package/app/composables/useDuxtVersion.ts +138 -0
- package/app/composables/useFuzzySearch.ts +83 -0
- package/app/composables/usePackageManager.ts +4 -0
- package/app/composables/useRecentPages.ts +52 -0
- package/app/error.vue +133 -0
- package/app/layouts/changelog.vue +85 -0
- package/app/layouts/default.vue +23 -0
- package/app/layouts/docs.vue +62 -0
- package/app/layouts/reference.vue +85 -0
- package/app/lib/utils.ts +7 -0
- package/app/middleware/duxt-section-layout.global.ts +57 -0
- package/app/pages/[...slug].vue +441 -0
- package/app/pages/index.vue +479 -0
- package/app/types/duxt.d.ts +955 -0
- package/app/utils/aside.ts +16 -0
- package/app/utils/changelog.ts +182 -0
- package/app/utils/code-highlight.ts +116 -0
- package/app/utils/duxt-config.ts +185 -0
- package/app/utils/duxt-text.ts +135 -0
- package/app/utils/file-icons.ts +62 -0
- package/app/utils/generated-sections.ts +190 -0
- package/app/utils/generated-toc.ts +216 -0
- package/app/utils/heading-link.ts +32 -0
- package/app/utils/locale-path.ts +45 -0
- package/app/utils/navigation-tree.ts +206 -0
- package/app/utils/nearest-page.ts +72 -0
- package/app/utils/openapi.ts +1028 -0
- package/app/utils/package-command.ts +162 -0
- package/app/utils/request-samples.ts +635 -0
- package/app/utils/section-scope.ts +97 -0
- package/app/utils/sheet-navigation.ts +100 -0
- package/app/utils/source-file.ts +84 -0
- package/app/utils/version-choices.ts +68 -0
- package/app/utils/version-paths.ts +65 -0
- package/bin/duxt-report.mjs +45 -0
- package/components.json +19 -0
- package/content-cache.ts +193 -0
- package/content.config.ts +38 -0
- package/duxt-app-config.ts +124 -0
- package/highlight-langs.ts +68 -0
- package/i18n/i18n.config.ts +16 -0
- package/i18n/locales/de/duxt/changelog.json +17 -0
- package/i18n/locales/de/duxt/code.json +13 -0
- package/i18n/locales/de/duxt/defaults.json +22 -0
- package/i18n/locales/de/duxt/devtools.json +7 -0
- package/i18n/locales/de/duxt/error.json +10 -0
- package/i18n/locales/de/duxt/footer.json +7 -0
- package/i18n/locales/de/duxt/locale.json +7 -0
- package/i18n/locales/de/duxt/nav.json +17 -0
- package/i18n/locales/de/duxt/openapi.json +89 -0
- package/i18n/locales/de/duxt/page.json +34 -0
- package/i18n/locales/de/duxt/search.json +12 -0
- package/i18n/locales/de/duxt/shortcuts.json +11 -0
- package/i18n/locales/de/duxt/theme.json +7 -0
- package/i18n/locales/de/duxt/toc.json +7 -0
- package/i18n/locales/de/duxt/version.json +25 -0
- package/i18n/locales/en/duxt/changelog.json +17 -0
- package/i18n/locales/en/duxt/code.json +13 -0
- package/i18n/locales/en/duxt/defaults.json +22 -0
- package/i18n/locales/en/duxt/devtools.json +7 -0
- package/i18n/locales/en/duxt/error.json +10 -0
- package/i18n/locales/en/duxt/footer.json +7 -0
- package/i18n/locales/en/duxt/locale.json +7 -0
- package/i18n/locales/en/duxt/nav.json +17 -0
- package/i18n/locales/en/duxt/openapi.json +89 -0
- package/i18n/locales/en/duxt/page.json +34 -0
- package/i18n/locales/en/duxt/search.json +12 -0
- package/i18n/locales/en/duxt/shortcuts.json +11 -0
- package/i18n/locales/en/duxt/theme.json +7 -0
- package/i18n/locales/en/duxt/toc.json +7 -0
- package/i18n/locales/en/duxt/version.json +25 -0
- package/i18n/locales/es/duxt/changelog.json +17 -0
- package/i18n/locales/es/duxt/code.json +13 -0
- package/i18n/locales/es/duxt/defaults.json +22 -0
- package/i18n/locales/es/duxt/devtools.json +7 -0
- package/i18n/locales/es/duxt/error.json +10 -0
- package/i18n/locales/es/duxt/footer.json +7 -0
- package/i18n/locales/es/duxt/locale.json +7 -0
- package/i18n/locales/es/duxt/nav.json +17 -0
- package/i18n/locales/es/duxt/openapi.json +89 -0
- package/i18n/locales/es/duxt/page.json +34 -0
- package/i18n/locales/es/duxt/search.json +12 -0
- package/i18n/locales/es/duxt/shortcuts.json +11 -0
- package/i18n/locales/es/duxt/theme.json +7 -0
- package/i18n/locales/es/duxt/toc.json +7 -0
- package/i18n/locales/es/duxt/version.json +25 -0
- package/i18n/locales/fr/duxt/changelog.json +17 -0
- package/i18n/locales/fr/duxt/code.json +13 -0
- package/i18n/locales/fr/duxt/defaults.json +22 -0
- package/i18n/locales/fr/duxt/devtools.json +7 -0
- package/i18n/locales/fr/duxt/error.json +10 -0
- package/i18n/locales/fr/duxt/footer.json +7 -0
- package/i18n/locales/fr/duxt/locale.json +7 -0
- package/i18n/locales/fr/duxt/nav.json +17 -0
- package/i18n/locales/fr/duxt/openapi.json +89 -0
- package/i18n/locales/fr/duxt/page.json +34 -0
- package/i18n/locales/fr/duxt/search.json +12 -0
- package/i18n/locales/fr/duxt/shortcuts.json +11 -0
- package/i18n/locales/fr/duxt/theme.json +7 -0
- package/i18n/locales/fr/duxt/toc.json +7 -0
- package/i18n/locales/fr/duxt/version.json +25 -0
- package/i18n/locales/pt/duxt/changelog.json +17 -0
- package/i18n/locales/pt/duxt/code.json +13 -0
- package/i18n/locales/pt/duxt/defaults.json +22 -0
- package/i18n/locales/pt/duxt/devtools.json +7 -0
- package/i18n/locales/pt/duxt/error.json +10 -0
- package/i18n/locales/pt/duxt/footer.json +7 -0
- package/i18n/locales/pt/duxt/locale.json +7 -0
- package/i18n/locales/pt/duxt/nav.json +17 -0
- package/i18n/locales/pt/duxt/openapi.json +89 -0
- package/i18n/locales/pt/duxt/page.json +34 -0
- package/i18n/locales/pt/duxt/search.json +12 -0
- package/i18n/locales/pt/duxt/shortcuts.json +11 -0
- package/i18n/locales/pt/duxt/theme.json +7 -0
- package/i18n/locales/pt/duxt/toc.json +7 -0
- package/i18n/locales/pt/duxt/version.json +25 -0
- package/i18n/locales/pt-BR/duxt/error.json +8 -0
- package/i18n/locales/pt-BR/duxt/nav.json +10 -0
- package/i18n/locales/pt-BR/duxt/page.json +10 -0
- package/i18n/locales/pt-BR/duxt/search.json +11 -0
- package/i18n/locales/pt-BR/duxt/version.json +13 -0
- package/mdc.config.ts +28 -0
- package/modules/config.ts +440 -0
- package/modules/devtools.ts +126 -0
- package/modules/git-meta.ts +349 -0
- package/modules/redirects.ts +152 -0
- package/modules/validate.ts +137 -0
- package/nuxt.config.ts +530 -0
- package/openapi-model.ts +358 -0
- package/openapi-parse.ts +1052 -0
- package/package.json +161 -0
- package/public/devtools/cache.html +318 -0
- package/public/devtools/checks.html +309 -0
- package/public/devtools/config.html +461 -0
- package/public/devtools/i18n.html +309 -0
- package/public/devtools/pages.html +348 -0
- package/public/devtools/paths.html +315 -0
- package/public/devtools/redirects.html +309 -0
- package/public/devtools/search.html +324 -0
- package/public/devtools/sources.html +309 -0
- package/public/devtools/versions.html +318 -0
- package/report.ts +303 -0
- package/repository-root.ts +26 -0
- package/section-reports.ts +55 -0
- package/sections-changelog.ts +644 -0
- package/sections-openapi.ts +450 -0
- package/sections-resolve.ts +711 -0
- package/sections.ts +157 -0
- package/server/devtools/content.ts +120 -0
- package/server/devtools/context.ts +38 -0
- package/server/devtools/entry-path.ts +30 -0
- package/server/devtools/handler.ts +104 -0
- package/server/devtools/preview.ts +461 -0
- package/server/devtools/render/content.ts +233 -0
- package/server/devtools/render/sources.ts +414 -0
- package/server/devtools/render/system.ts +352 -0
- package/server/devtools/shell.ts +611 -0
- package/server/devtools/sources.ts +122 -0
- package/server/devtools/system.ts +178 -0
- package/server/mcp/tools/list-pages.ts +52 -0
- package/server/mcp/tools/read-page.ts +57 -0
- package/server/mcp/tools/search-docs.ts +74 -0
- package/server/middleware/raw-markdown.ts +86 -0
- package/server/routes/llms-full.txt.get.ts +75 -0
- package/server/routes/llms.txt.get.ts +63 -0
- package/server/routes/rss.xml.get.ts +125 -0
- package/server/utils/duxt-server-text.ts +46 -0
- package/sources-git.ts +113 -0
- package/sources-resolve.ts +908 -0
- package/sources.ts +287 -0
- package/validate-report.ts +494 -0
package/sections.ts
ADDED
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generated sections, as Content collections.
|
|
3
|
+
*
|
|
4
|
+
* The build-time half of `sections-resolve.ts`, split from it for the reason
|
|
5
|
+
* `sources.ts` is split from `sources-resolve.ts`: this file imports
|
|
6
|
+
* `@nuxt/content` and reads files off disk, and the bundler refuses to follow
|
|
7
|
+
* either out of app code.
|
|
8
|
+
*
|
|
9
|
+
* A section is an ORDINARY collection — nothing here mounts a route, adds a
|
|
10
|
+
* hook or reaches into the renderer. What makes that possible is Content's
|
|
11
|
+
* custom collection source: `getKeys` and `getItem` hand it a list of files and
|
|
12
|
+
* their contents, so a type's parser can turn one artefact into as many pages as
|
|
13
|
+
* it has releases, endpoints or records, and Content parses them exactly as it
|
|
14
|
+
* parses Markdown on disk.
|
|
15
|
+
*
|
|
16
|
+
* Reading a file is all that is left here. What a finding MEANS — a missing
|
|
17
|
+
* artefact, a type that read nothing out of one — is the severity policy, and
|
|
18
|
+
* it lives in the pure half where a test can reach it.
|
|
19
|
+
*/
|
|
20
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
21
|
+
import { join } from 'node:path';
|
|
22
|
+
import { defineCollection, defineCollectionSource } from '@nuxt/content';
|
|
23
|
+
import type {
|
|
24
|
+
DuxtResolvedSource,
|
|
25
|
+
DuxtSource,
|
|
26
|
+
DuxtSourcesOptions
|
|
27
|
+
} from './sources-resolve';
|
|
28
|
+
import type {
|
|
29
|
+
DuxtSectionPage,
|
|
30
|
+
DuxtSectionType,
|
|
31
|
+
DuxtSectionTypes
|
|
32
|
+
} from './sections-resolve';
|
|
33
|
+
import {
|
|
34
|
+
duxtSectionTypes,
|
|
35
|
+
generatedSectionRef,
|
|
36
|
+
missingSectionArtefact,
|
|
37
|
+
resolveGeneratedSections,
|
|
38
|
+
sectionPages
|
|
39
|
+
} from './sections-resolve';
|
|
40
|
+
import { resolveLatestRefs } from './sources-git';
|
|
41
|
+
import { pageSchema, repositoryRoot } from './sources';
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* One collection per declared section.
|
|
45
|
+
*
|
|
46
|
+
* Composed beside `duxtSources` rather than inside it: both read the same
|
|
47
|
+
* source list, and keeping them apart is what lets `duxtSources` walk its own
|
|
48
|
+
* result index-for-index against `expandSources`.
|
|
49
|
+
*/
|
|
50
|
+
export function duxtGeneratedCollections(
|
|
51
|
+
input: DuxtSource[],
|
|
52
|
+
options: DuxtSourcesOptions = {},
|
|
53
|
+
types: DuxtSectionTypes = duxtSectionTypes()
|
|
54
|
+
) {
|
|
55
|
+
// The same `latest` resolution both other entry points do. Cached per
|
|
56
|
+
// repository for the life of the process, so asking again is free and cannot
|
|
57
|
+
// straddle a release.
|
|
58
|
+
const sources = resolveLatestRefs(input);
|
|
59
|
+
const collections: Record<string, ReturnType<typeof defineCollection>> = {};
|
|
60
|
+
|
|
61
|
+
for (const entry of resolveGeneratedSections(sources, options, types)) {
|
|
62
|
+
const type = types[entry.generated!.type]!;
|
|
63
|
+
|
|
64
|
+
collections[entry.collection] = entry.generated!.remote
|
|
65
|
+
? remoteCollection(entry, type)
|
|
66
|
+
: localCollection(entry, type);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return collections;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* A section whose artefact is in the repository being built.
|
|
74
|
+
*
|
|
75
|
+
* Read and parsed HERE, while the config is being loaded, rather than lazily:
|
|
76
|
+
* the file is on disk already, and a missing one is an error this build must
|
|
77
|
+
* fail on — see `missingSectionArtefact`. Failing at config load says which
|
|
78
|
+
* section and which path, where the same throw from inside the parse pass says
|
|
79
|
+
* neither.
|
|
80
|
+
*/
|
|
81
|
+
function localCollection(entry: DuxtResolvedSource, type: DuxtSectionType) {
|
|
82
|
+
const file = join(repositoryRoot(), entry.path);
|
|
83
|
+
|
|
84
|
+
const pages = existsSync(file)
|
|
85
|
+
? sectionPages(entry, type, readFileSync(file, 'utf8'))
|
|
86
|
+
: missingSectionArtefact(entry, file);
|
|
87
|
+
|
|
88
|
+
const source = defineCollectionSource({
|
|
89
|
+
getKeys: async () => pages.map((page) => page.file),
|
|
90
|
+
getItem: async (key) => body(pages, key)
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
// The URL these pages are served at. Content builds a page's path from its
|
|
94
|
+
// key with the source's prefix in front, which is the whole reason a section
|
|
95
|
+
// needs no route of its own.
|
|
96
|
+
source.prefix = entry.prefix;
|
|
97
|
+
|
|
98
|
+
return defineCollection({ type: 'page', schema: pageSchema, source });
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* A section whose artefact is in a repository Content downloads.
|
|
103
|
+
*
|
|
104
|
+
* The download is Content's, not this layer's: the collection is declared with
|
|
105
|
+
* the ordinary `repository` source, and only `getKeys`/`getItem` are replaced.
|
|
106
|
+
* That is deliberate — where the checkout lands under `.data/content/` is
|
|
107
|
+
* Content's own hash-cached answer, and a second implementation of it would be
|
|
108
|
+
* a directory name that drifts on the first release.
|
|
109
|
+
*
|
|
110
|
+
* Lazy, because the checkout does not exist until Content has run `prepare`.
|
|
111
|
+
*/
|
|
112
|
+
function remoteCollection(entry: DuxtResolvedSource, type: DuxtSectionType) {
|
|
113
|
+
const ref = generatedSectionRef(entry);
|
|
114
|
+
const url = entry.repositoryUrl!;
|
|
115
|
+
|
|
116
|
+
const collection = defineCollection({
|
|
117
|
+
type: 'page',
|
|
118
|
+
schema: pageSchema,
|
|
119
|
+
source: {
|
|
120
|
+
repository: ref
|
|
121
|
+
? 'tag' in ref
|
|
122
|
+
? { url, tag: ref.tag }
|
|
123
|
+
: { url, branch: ref.branch }
|
|
124
|
+
: url,
|
|
125
|
+
include: entry.path
|
|
126
|
+
}
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
const source = collection.source![0]!;
|
|
130
|
+
source.prefix = entry.prefix;
|
|
131
|
+
|
|
132
|
+
let pages: DuxtSectionPage[] | undefined;
|
|
133
|
+
|
|
134
|
+
const read = () => {
|
|
135
|
+
if (pages) return pages;
|
|
136
|
+
|
|
137
|
+
// `cwd` is where Content put the checkout, filled in by the `prepare` it
|
|
138
|
+
// installed on this source and run before the first `getKeys`.
|
|
139
|
+
const file = join(source.cwd, entry.path);
|
|
140
|
+
|
|
141
|
+
pages = existsSync(file)
|
|
142
|
+
? sectionPages(entry, type, readFileSync(file, 'utf8'))
|
|
143
|
+
: missingSectionArtefact(entry, file);
|
|
144
|
+
|
|
145
|
+
return pages;
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
source.getKeys = async () => read().map((page) => page.file);
|
|
149
|
+
source.getItem = async (key) => body(read(), key);
|
|
150
|
+
|
|
151
|
+
return collection;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/** One page's file, by name. */
|
|
155
|
+
function body(pages: DuxtSectionPage[], key: string): string {
|
|
156
|
+
return pages.find((page) => page.file === key)?.body ?? '';
|
|
157
|
+
}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import type { H3Event } from 'h3';
|
|
2
|
+
import {
|
|
3
|
+
queryCollection,
|
|
4
|
+
queryCollectionSearchSections
|
|
5
|
+
} from '@nuxt/content/nitro';
|
|
6
|
+
import type { Collected, PageRecord } from '../../validate-report';
|
|
7
|
+
import { walk } from '../../validate-report';
|
|
8
|
+
import { context, resolvedSources } from './context';
|
|
9
|
+
import type { Doc, DocGroup, IndexedSection } from './render/content';
|
|
10
|
+
import { renderChecks, renderPages, renderSearch } from './render/content';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* The queries behind the three content panels.
|
|
14
|
+
*
|
|
15
|
+
* All three ask the running site — `queryCollection` against the database the
|
|
16
|
+
* pages render from — instead of re-reading Markdown off disk. A panel that
|
|
17
|
+
* parsed its own copy would answer about a site that does not exist. What comes
|
|
18
|
+
* back goes to `render/content.ts`, which turns it into the tables.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
async function docsOf(event: H3Event, fields?: string[]): Promise<DocGroup[]> {
|
|
22
|
+
const sources = resolvedSources();
|
|
23
|
+
|
|
24
|
+
return Promise.all(
|
|
25
|
+
sources.map(async (source) => {
|
|
26
|
+
try {
|
|
27
|
+
const query = queryCollection(
|
|
28
|
+
event,
|
|
29
|
+
source.collection as Parameters<typeof queryCollection>[1]
|
|
30
|
+
);
|
|
31
|
+
const docs = (await (
|
|
32
|
+
fields ? query.select(...(fields as ['path'])) : query
|
|
33
|
+
)
|
|
34
|
+
.order('path', 'ASC')
|
|
35
|
+
.all()) as unknown as Doc[];
|
|
36
|
+
|
|
37
|
+
return { collection: source.collection, docs };
|
|
38
|
+
} catch {
|
|
39
|
+
return { collection: source.collection, docs: [] };
|
|
40
|
+
}
|
|
41
|
+
})
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** Every page, with what the frontmatter and the git history gave it. */
|
|
46
|
+
export async function pagesPanel(event: H3Event): Promise<string> {
|
|
47
|
+
const groups = await docsOf(event, [
|
|
48
|
+
'path',
|
|
49
|
+
'title',
|
|
50
|
+
'description',
|
|
51
|
+
'icon',
|
|
52
|
+
'id',
|
|
53
|
+
'lastUpdated',
|
|
54
|
+
'contributors'
|
|
55
|
+
]);
|
|
56
|
+
|
|
57
|
+
return renderPages(groups, context.rootDir);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** The build validator, run against the served site. */
|
|
61
|
+
export async function checksPanel(event: H3Event): Promise<string> {
|
|
62
|
+
const sources = resolvedSources();
|
|
63
|
+
const groups = await docsOf(event);
|
|
64
|
+
|
|
65
|
+
const pages: PageRecord[] = groups.flatMap(({ collection, docs }) =>
|
|
66
|
+
docs.map((doc) => {
|
|
67
|
+
const collected: Collected = {
|
|
68
|
+
anchors: new Set<string>(),
|
|
69
|
+
links: [],
|
|
70
|
+
commands: []
|
|
71
|
+
};
|
|
72
|
+
walk(doc.body, collected);
|
|
73
|
+
|
|
74
|
+
return {
|
|
75
|
+
collection,
|
|
76
|
+
path: doc.path,
|
|
77
|
+
file: doc.id ?? doc.path,
|
|
78
|
+
title: doc.title,
|
|
79
|
+
description: doc.description,
|
|
80
|
+
anchors: collected.anchors,
|
|
81
|
+
links: collected.links,
|
|
82
|
+
commands: collected.commands
|
|
83
|
+
};
|
|
84
|
+
})
|
|
85
|
+
);
|
|
86
|
+
|
|
87
|
+
return renderChecks(sources, pages, context.rootDir);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/** What the search index holds, through the same query the client runs. */
|
|
91
|
+
export async function searchPanel(
|
|
92
|
+
event: H3Event,
|
|
93
|
+
term: string
|
|
94
|
+
): Promise<string> {
|
|
95
|
+
const sources = resolvedSources();
|
|
96
|
+
|
|
97
|
+
const sections = (
|
|
98
|
+
await Promise.all(
|
|
99
|
+
sources.map(async (source) => {
|
|
100
|
+
try {
|
|
101
|
+
const found = await queryCollectionSearchSections(
|
|
102
|
+
event,
|
|
103
|
+
source.collection as Parameters<
|
|
104
|
+
typeof queryCollectionSearchSections
|
|
105
|
+
>[1]
|
|
106
|
+
);
|
|
107
|
+
|
|
108
|
+
return found.map((section) => ({
|
|
109
|
+
collection: source.collection,
|
|
110
|
+
...section
|
|
111
|
+
})) as IndexedSection[];
|
|
112
|
+
} catch {
|
|
113
|
+
return [] as IndexedSection[];
|
|
114
|
+
}
|
|
115
|
+
})
|
|
116
|
+
)
|
|
117
|
+
).flat();
|
|
118
|
+
|
|
119
|
+
return renderSearch(term, sections);
|
|
120
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
// @ts-expect-error virtual module, generated in modules/devtools.ts
|
|
2
|
+
import { devtools } from '#duxt-devtools';
|
|
3
|
+
import type { DevtoolsContext } from './shell';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* The two things a panel cannot render itself: where this build sits on disk,
|
|
7
|
+
* and what the running app config resolved to.
|
|
8
|
+
*
|
|
9
|
+
* Split from `shell.ts` so that file stays free of both the virtual module and
|
|
10
|
+
* the Nitro auto-imports. Everything in `shell.ts` and `render/` is then plain
|
|
11
|
+
* data in, HTML out — importable from a plain Node script, which is how the
|
|
12
|
+
* documentation renders a panel of its own with fixture data instead of a
|
|
13
|
+
* screenshot that goes stale.
|
|
14
|
+
*/
|
|
15
|
+
export const context = devtools as DevtoolsContext;
|
|
16
|
+
|
|
17
|
+
/** The collections the site is serving, straight out of the running app config. */
|
|
18
|
+
export function resolvedSources(): DuxtResolvedSource[] {
|
|
19
|
+
const { duxt } = useAppConfig() as { duxt?: Partial<DuxtConfig> };
|
|
20
|
+
|
|
21
|
+
return (
|
|
22
|
+
duxt?.resolvedSources ?? [
|
|
23
|
+
{
|
|
24
|
+
collection: 'docs',
|
|
25
|
+
prefix: '',
|
|
26
|
+
path: 'docs',
|
|
27
|
+
isDefault: true,
|
|
28
|
+
// The fallback is the site's only source, so it is also the language
|
|
29
|
+
// everything falls back to — the field is not optional on the manifest.
|
|
30
|
+
isDefaultLocale: true,
|
|
31
|
+
status: 'current',
|
|
32
|
+
// A source read off disk is a full checkout, so its history is
|
|
33
|
+
// readable without asking.
|
|
34
|
+
history: true
|
|
35
|
+
}
|
|
36
|
+
]
|
|
37
|
+
);
|
|
38
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { join, resolve } from 'node:path';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Resolve a cache entry the tab may delete, or refuse.
|
|
5
|
+
*
|
|
6
|
+
* Its own file, importing nothing but `node:path`, for the same reason
|
|
7
|
+
* `validate-report.ts` is its own file: everything else under `devtools/`
|
|
8
|
+
* reaches the virtual module the build generates, and a guard that cannot be
|
|
9
|
+
* tested without booting Nitro is a guard nobody re-checks. This is the one
|
|
10
|
+
* destructive thing the panel can do, so it is the one part that is proven.
|
|
11
|
+
*
|
|
12
|
+
* The name arrives from a form field, so it is checked rather than trusted: the
|
|
13
|
+
* result has to be a DIRECT child of the cache directory. A traversal, an
|
|
14
|
+
* absolute path, a nested path or an empty name all resolve somewhere else,
|
|
15
|
+
* which is why the comparison is on the resolved parent and never on the string
|
|
16
|
+
* that was submitted.
|
|
17
|
+
*/
|
|
18
|
+
export function cacheEntryToDrop(
|
|
19
|
+
dataDir: string,
|
|
20
|
+
name: unknown
|
|
21
|
+
): string | undefined {
|
|
22
|
+
if (typeof name !== 'string' || !name || name === '.' || name === '..') {
|
|
23
|
+
return undefined;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const target = resolve(dataDir, name);
|
|
27
|
+
if (resolve(join(target, '..')) !== resolve(dataDir)) return undefined;
|
|
28
|
+
|
|
29
|
+
return target;
|
|
30
|
+
}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { rmSync } from 'node:fs';
|
|
2
|
+
import type { H3Event } from 'h3';
|
|
3
|
+
import { context } from './context';
|
|
4
|
+
import { page, PANELS } from './shell';
|
|
5
|
+
import { pathsPanel, sourcesPanel, versionsPanel } from './sources';
|
|
6
|
+
import { checksPanel, pagesPanel, searchPanel } from './content';
|
|
7
|
+
import { cacheEntryToDrop } from './entry-path';
|
|
8
|
+
import { cachePanel, configPanel, i18nPanel, redirectsPanel } from './system';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* The devtools tab, as one route with a panel per path segment.
|
|
12
|
+
*
|
|
13
|
+
* NOT under `server/routes/`, and that is the point. Nitro scans a layer's
|
|
14
|
+
* `server/routes/` and registers what it finds — in every build, dev or not —
|
|
15
|
+
* so a file there is a production route no matter what the module that
|
|
16
|
+
* "registers" it decides. The tab shipped that way once: `/_duxt/devtools` was
|
|
17
|
+
* in the production server bundle, serving the resolved configuration, absolute
|
|
18
|
+
* filesystem paths and a POST that deletes a directory, while
|
|
19
|
+
* `modules/devtools.ts` returned early believing it had prevented exactly that.
|
|
20
|
+
* Sitting outside the scanned directory, the handler exists only because the
|
|
21
|
+
* dev-only module points Nitro at it by path. Everything the panels show is computed per
|
|
22
|
+
* request out of the RUNNING app config and the served database, not out of a
|
|
23
|
+
* snapshot taken when the module loaded: the first version froze its data into
|
|
24
|
+
* a virtual module, and editing `app.config.ts` then left the tab confidently
|
|
25
|
+
* describing the previous state, which is the one thing a debugging view must
|
|
26
|
+
* never do.
|
|
27
|
+
*/
|
|
28
|
+
export default defineEventHandler(async (event) => {
|
|
29
|
+
const slug = ((event.context.params?._ as string | undefined) ?? '').replace(
|
|
30
|
+
/^\/+|\/+$/g,
|
|
31
|
+
''
|
|
32
|
+
);
|
|
33
|
+
|
|
34
|
+
if (slug && !PANELS.some((panel) => panel.slug === slug)) {
|
|
35
|
+
setResponseStatus(event, 404);
|
|
36
|
+
return html(page('', '<div class="note">No such panel.</div>'));
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// The one mutation in the tab: dropping a cache entry. A POST because it
|
|
40
|
+
// deletes, so a reload or a prefetch cannot trigger it.
|
|
41
|
+
if (event.method === 'POST' && slug === 'cache') {
|
|
42
|
+
const body = (await readBody(event)) as { drop?: string } | undefined;
|
|
43
|
+
const target = cacheEntryToDrop(context.dataDir, body?.drop);
|
|
44
|
+
|
|
45
|
+
if (!target) {
|
|
46
|
+
return html(
|
|
47
|
+
page(
|
|
48
|
+
slug,
|
|
49
|
+
cachePanel('That name is not an entry of the cache directory.')
|
|
50
|
+
)
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
rmSync(target, { recursive: true, force: true });
|
|
55
|
+
|
|
56
|
+
return html(
|
|
57
|
+
page(
|
|
58
|
+
slug,
|
|
59
|
+
cachePanel(`Dropped ${body?.drop}. The next build re-downloads it.`)
|
|
60
|
+
)
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const query = getQuery(event) as { path?: string; q?: string };
|
|
65
|
+
|
|
66
|
+
const body = await panelBody(event, slug, query);
|
|
67
|
+
|
|
68
|
+
return html(page(slug, body));
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
function panelBody(
|
|
72
|
+
event: H3Event,
|
|
73
|
+
slug: string,
|
|
74
|
+
query: { path?: string; q?: string }
|
|
75
|
+
): string | Promise<string> {
|
|
76
|
+
switch (slug) {
|
|
77
|
+
case 'paths':
|
|
78
|
+
return pathsPanel(event, query.path ?? '');
|
|
79
|
+
case 'pages':
|
|
80
|
+
return pagesPanel(event);
|
|
81
|
+
case 'versions':
|
|
82
|
+
return versionsPanel(event);
|
|
83
|
+
case 'checks':
|
|
84
|
+
return checksPanel(event);
|
|
85
|
+
case 'search':
|
|
86
|
+
return searchPanel(event, query.q ?? '');
|
|
87
|
+
case 'config':
|
|
88
|
+
return configPanel();
|
|
89
|
+
case 'i18n':
|
|
90
|
+
return i18nPanel();
|
|
91
|
+
case 'cache':
|
|
92
|
+
return cachePanel();
|
|
93
|
+
case 'redirects':
|
|
94
|
+
return redirectsPanel();
|
|
95
|
+
default:
|
|
96
|
+
return sourcesPanel();
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function html(body: string) {
|
|
101
|
+
return new Response(body, {
|
|
102
|
+
headers: { 'content-type': 'text/html; charset=utf-8' }
|
|
103
|
+
});
|
|
104
|
+
}
|