@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
|
@@ -0,0 +1,63 @@
|
|
|
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 { resolveServerTexts } from '../utils/duxt-server-text';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* llms.txt — the site's own map, for a model reading it.
|
|
10
|
+
*
|
|
11
|
+
* The convention (llmstxt.org) is a Markdown index: an H1 with the site name,
|
|
12
|
+
* a blockquote summary, then linked sections. It is generated from the same
|
|
13
|
+
* collection the pages render from, so it cannot drift from what is published.
|
|
14
|
+
*/
|
|
15
|
+
export default defineEventHandler(async (event) => {
|
|
16
|
+
const appConfig = useAppConfig() as { duxt?: Partial<DuxtConfig> };
|
|
17
|
+
// Resolved here because Nitro has no i18n: without it the description is
|
|
18
|
+
// printed as the translation key it is.
|
|
19
|
+
const duxt = resolveServerTexts(
|
|
20
|
+
mergeDuxtConfig(appConfig.duxt, duxtDefaults)
|
|
21
|
+
);
|
|
22
|
+
|
|
23
|
+
// Every collection the manifest names, not just `docs`: a versioned site
|
|
24
|
+
// has none by that name, and llms.txt is the whole site's index.
|
|
25
|
+
const collections = duxt.resolvedSources?.length
|
|
26
|
+
? duxt.resolvedSources.map((source) => source.collection)
|
|
27
|
+
: ['docs'];
|
|
28
|
+
|
|
29
|
+
const pages = (
|
|
30
|
+
await Promise.all(
|
|
31
|
+
collections.map((name) =>
|
|
32
|
+
// Cast from Content's own signature: the collection name is data
|
|
33
|
+
// from the manifest, and the key union is generated per site.
|
|
34
|
+
queryCollection(event, name as Parameters<typeof queryCollection>[1])
|
|
35
|
+
.select('path', 'title', 'description')
|
|
36
|
+
.all()
|
|
37
|
+
)
|
|
38
|
+
)
|
|
39
|
+
).flat();
|
|
40
|
+
|
|
41
|
+
const origin = getRequestURL(event).origin;
|
|
42
|
+
|
|
43
|
+
const lines = [
|
|
44
|
+
`# ${duxt.title}`,
|
|
45
|
+
'',
|
|
46
|
+
`> ${duxt.landing?.description ?? 'Documentation.'}`,
|
|
47
|
+
'',
|
|
48
|
+
'## Pages',
|
|
49
|
+
'',
|
|
50
|
+
...pages
|
|
51
|
+
.filter((page) => page.path)
|
|
52
|
+
.sort((a, b) => a.path!.localeCompare(b.path!))
|
|
53
|
+
.map(
|
|
54
|
+
(page) =>
|
|
55
|
+
`- [${page.title ?? page.path}](${origin}${page.path})` +
|
|
56
|
+
(page.description ? `: ${page.description}` : '')
|
|
57
|
+
),
|
|
58
|
+
''
|
|
59
|
+
];
|
|
60
|
+
|
|
61
|
+
setHeader(event, 'content-type', 'text/plain; charset=utf-8');
|
|
62
|
+
return lines.join('\n');
|
|
63
|
+
});
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import { queryCollection } from '@nuxt/content/nitro';
|
|
2
|
+
import { duxtDefaults, mergeDuxtConfig } from '../../app/utils/duxt-config';
|
|
3
|
+
import { resolveServerTexts } from '../utils/duxt-server-text';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* An RSS feed over one section — a changelog, a release log, a blog.
|
|
7
|
+
*
|
|
8
|
+
* Not over the whole documentation. A feed is a list of things that HAPPENED,
|
|
9
|
+
* and a reference page being edited is not an event; a site publishing every
|
|
10
|
+
* page edit as an item teaches its readers to unsubscribe. So the feed is off
|
|
11
|
+
* until `duxt.feed.path` names the section whose pages are events, and empty
|
|
12
|
+
* rather than missing before that: a feed client should be told there is
|
|
13
|
+
* nothing here, not that the feed is gone.
|
|
14
|
+
*
|
|
15
|
+
* Ordering is by the page's own `date`, falling back to the last commit that
|
|
16
|
+
* touched it — which `modules/git-meta.ts` has already put on the page.
|
|
17
|
+
*/
|
|
18
|
+
const escape = (value: string) =>
|
|
19
|
+
value.replace(
|
|
20
|
+
/[&<>"']/g,
|
|
21
|
+
(character) =>
|
|
22
|
+
({
|
|
23
|
+
'&': '&',
|
|
24
|
+
'<': '<',
|
|
25
|
+
'>': '>',
|
|
26
|
+
'"': '"',
|
|
27
|
+
"'": '''
|
|
28
|
+
})[character] ?? character
|
|
29
|
+
);
|
|
30
|
+
|
|
31
|
+
export default defineEventHandler(async (event) => {
|
|
32
|
+
const appConfig = useAppConfig() as { duxt?: Partial<DuxtConfig> };
|
|
33
|
+
// Resolved here because Nitro has no i18n: without it the description is
|
|
34
|
+
// printed as the translation key it is.
|
|
35
|
+
const duxt = resolveServerTexts(
|
|
36
|
+
mergeDuxtConfig(appConfig.duxt, duxtDefaults)
|
|
37
|
+
);
|
|
38
|
+
|
|
39
|
+
const origin = getRequestURL(event).origin;
|
|
40
|
+
const feed = duxt.feed;
|
|
41
|
+
const title = typeof feed?.title === 'string' ? feed.title : undefined;
|
|
42
|
+
|
|
43
|
+
const items: {
|
|
44
|
+
path: string;
|
|
45
|
+
title: string;
|
|
46
|
+
description?: string;
|
|
47
|
+
date?: string;
|
|
48
|
+
}[] = [];
|
|
49
|
+
|
|
50
|
+
if (feed?.path) {
|
|
51
|
+
const collections = duxt.resolvedSources?.length
|
|
52
|
+
? duxt.resolvedSources
|
|
53
|
+
// One version of a changelog, not four. The default version is the
|
|
54
|
+
// one a feed reader wants; the others repeat every entry.
|
|
55
|
+
.filter((source) => source.isDefault)
|
|
56
|
+
.map((source) => source.collection)
|
|
57
|
+
: ['docs'];
|
|
58
|
+
|
|
59
|
+
const pages = (
|
|
60
|
+
await Promise.all(
|
|
61
|
+
collections.map((name) =>
|
|
62
|
+
queryCollection(event, name as Parameters<typeof queryCollection>[1])
|
|
63
|
+
.select('path', 'title', 'description', 'date', 'lastUpdated')
|
|
64
|
+
.all()
|
|
65
|
+
)
|
|
66
|
+
)
|
|
67
|
+
).flat() as {
|
|
68
|
+
path?: string;
|
|
69
|
+
title?: string;
|
|
70
|
+
description?: string;
|
|
71
|
+
date?: string;
|
|
72
|
+
lastUpdated?: string;
|
|
73
|
+
}[];
|
|
74
|
+
|
|
75
|
+
for (const page of pages) {
|
|
76
|
+
if (!page.path?.startsWith(feed.path)) continue;
|
|
77
|
+
if (page.path === feed.path) continue;
|
|
78
|
+
|
|
79
|
+
items.push({
|
|
80
|
+
path: page.path,
|
|
81
|
+
title: page.title ?? page.path,
|
|
82
|
+
description: page.description,
|
|
83
|
+
date: page.date ?? page.lastUpdated
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
items.sort((a, b) => (b.date ?? '').localeCompare(a.date ?? ''));
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const body = items
|
|
91
|
+
.map(
|
|
92
|
+
(item) => ` <item>
|
|
93
|
+
<title>${escape(item.title)}</title>
|
|
94
|
+
<link>${escape(origin + item.path)}</link>
|
|
95
|
+
<guid isPermaLink="true">${escape(origin + item.path)}</guid>${
|
|
96
|
+
item.description
|
|
97
|
+
? `\n <description>${escape(item.description)}</description>`
|
|
98
|
+
: ''
|
|
99
|
+
}${
|
|
100
|
+
item.date
|
|
101
|
+
? `\n <pubDate>${new Date(item.date).toUTCString()}</pubDate>`
|
|
102
|
+
: ''
|
|
103
|
+
}
|
|
104
|
+
</item>`
|
|
105
|
+
)
|
|
106
|
+
.join('\n');
|
|
107
|
+
|
|
108
|
+
setHeader(event, 'content-type', 'application/rss+xml; charset=utf-8');
|
|
109
|
+
|
|
110
|
+
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
111
|
+
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
|
|
112
|
+
<channel>
|
|
113
|
+
<title>${escape(title ?? String(duxt.title))}</title>
|
|
114
|
+
<link>${escape(origin)}</link>
|
|
115
|
+
<description>${escape(
|
|
116
|
+
typeof duxt.landing?.description === 'string'
|
|
117
|
+
? duxt.landing.description
|
|
118
|
+
: 'Documentation.'
|
|
119
|
+
)}</description>
|
|
120
|
+
<atom:link href="${escape(`${origin}/rss.xml`)}" rel="self" type="application/rss+xml" />
|
|
121
|
+
${body}
|
|
122
|
+
</channel>
|
|
123
|
+
</rss>
|
|
124
|
+
`;
|
|
125
|
+
});
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import enDefaults from '../../i18n/locales/en/duxt/defaults.json' with { type: 'json' };
|
|
2
|
+
import { resolveDuxtTexts } from '../../app/utils/duxt-text';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Resolve the config's text fields on the server, where there is no i18n.
|
|
6
|
+
*
|
|
7
|
+
* `llms.txt` and the feed are Nitro routes: no Vue app, no `useI18n`, and so no
|
|
8
|
+
* translator. Before this, both printed the KEY — a feed whose description was
|
|
9
|
+
* the dotted path itself, and an llms.txt that told a model the same. Falling
|
|
10
|
+
* back to the layer's English messages is the honest answer: it
|
|
11
|
+
* is what `fallbackLocale` already says a missing translation resolves to, and
|
|
12
|
+
* these routes have no locale of their own to serve anyway.
|
|
13
|
+
*
|
|
14
|
+
* Only the layer's own defaults are looked up. A consumer's keys live in the
|
|
15
|
+
* consumer's locale files, which Nitro has no reader for — those pass through
|
|
16
|
+
* as written, exactly as an unregistered key does in the browser.
|
|
17
|
+
*/
|
|
18
|
+
const messages = enDefaults as Record<string, unknown>;
|
|
19
|
+
|
|
20
|
+
function lookup(key: string): string | undefined {
|
|
21
|
+
let node: unknown = messages;
|
|
22
|
+
|
|
23
|
+
for (const part of key.split('.')) {
|
|
24
|
+
if (!node || typeof node !== 'object') return undefined;
|
|
25
|
+
node = (node as Record<string, unknown>)[part];
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return typeof node === 'string' ? node : undefined;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function resolveServerTexts<T>(config: T): T {
|
|
32
|
+
return resolveDuxtTexts(config, 'en-GB', lookup);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* The Markdown body without its frontmatter block.
|
|
37
|
+
*
|
|
38
|
+
* Content's `rawbody` is the file as it sits on disk, frontmatter included. A
|
|
39
|
+
* reader wants the prose; a model handed `---\ntitle: …\n---` reads a page
|
|
40
|
+
* whose first heading is a YAML fence.
|
|
41
|
+
*/
|
|
42
|
+
export function stripFrontmatter(body: string): string {
|
|
43
|
+
const match = /^---\r?\n[\s\S]*?\r?\n---\r?\n?/.exec(body);
|
|
44
|
+
|
|
45
|
+
return match ? body.slice(match[0].length) : body;
|
|
46
|
+
}
|
package/sources-git.ts
ADDED
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { execFileSync } from 'node:child_process';
|
|
2
|
+
import type { DuxtSource } from './sources-resolve';
|
|
3
|
+
import { isLatestRef, newestTag, repoUrl } from './sources-resolve';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Resolve the `latest` shorthand into the tag it means.
|
|
7
|
+
*
|
|
8
|
+
* Node-only, and separate from `sources-resolve.ts` for the same reason
|
|
9
|
+
* `sources.ts` is: that file is read by the browser half of the layer, and
|
|
10
|
+
* `node:child_process` cannot be bundled for it.
|
|
11
|
+
*
|
|
12
|
+
* Both halves of the build must agree on the answer — `sources.ts` declares the
|
|
13
|
+
* collections, `modules/config.ts` resolves the manifest the theme reads — so
|
|
14
|
+
* the answer is cached per repository for the life of the process. Two calls to
|
|
15
|
+
* `git ls-remote` a second apart could otherwise straddle a release and leave
|
|
16
|
+
* the site with a collection no route points at.
|
|
17
|
+
*/
|
|
18
|
+
const cache = new Map<string, string[]>();
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* What a repository URL is allowed to look like before it is handed to `git`.
|
|
22
|
+
*
|
|
23
|
+
* An allowlist rather than a check for a leading dash, because the argument
|
|
24
|
+
* reaches a command: `git ls-remote --upload-pack=<anything>` runs that
|
|
25
|
+
* anything, and every value that is not a URL is easier to reject than to
|
|
26
|
+
* reason about. One quantifier over one character class, so the test itself
|
|
27
|
+
* cannot be the next finding.
|
|
28
|
+
*/
|
|
29
|
+
const REMOTE_URL = /^(?:https?:\/\/|ssh:\/\/|git@)[\w.~:/?#@!$&'()*+,;=%-]+$/;
|
|
30
|
+
|
|
31
|
+
/** The tags of a remote, or nothing when its URL is not one we will run. */
|
|
32
|
+
function remoteTags(repo: string): string {
|
|
33
|
+
const url = repoUrl(repo);
|
|
34
|
+
|
|
35
|
+
if (!REMOTE_URL.test(url)) {
|
|
36
|
+
throw new Error(`duxt: refusing to read tags from ${JSON.stringify(url)}`);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return execFileSync(
|
|
40
|
+
'git',
|
|
41
|
+
// `--end-of-options` so a URL that survived the test above is still read as
|
|
42
|
+
// a URL and never as an option.
|
|
43
|
+
['ls-remote', '--tags', '--refs', '--end-of-options', url],
|
|
44
|
+
{ encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] }
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function tagsOf(repo: string | undefined): string[] {
|
|
49
|
+
const key = repo ?? '.';
|
|
50
|
+
const cached = cache.get(key);
|
|
51
|
+
if (cached) return cached;
|
|
52
|
+
|
|
53
|
+
let tags: string[] = [];
|
|
54
|
+
|
|
55
|
+
try {
|
|
56
|
+
const output = repo
|
|
57
|
+
? remoteTags(repo)
|
|
58
|
+
: execFileSync('git', ['tag', '--list'], {
|
|
59
|
+
encoding: 'utf8',
|
|
60
|
+
stdio: ['ignore', 'pipe', 'ignore']
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
tags = output
|
|
64
|
+
.split('\n')
|
|
65
|
+
.map((line) => line.trim().split(/\s+/).pop() ?? '')
|
|
66
|
+
.map((ref) => ref.replace(/^refs\/tags\//, ''))
|
|
67
|
+
.filter(Boolean);
|
|
68
|
+
} catch {
|
|
69
|
+
// Reported by the caller, which knows which source asked.
|
|
70
|
+
tags = [];
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
cache.set(key, tags);
|
|
74
|
+
return tags;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* The source list with every `latest` replaced by a concrete tag.
|
|
79
|
+
*
|
|
80
|
+
* Called before `resolveSources` by both build-time entry points, so the
|
|
81
|
+
* resolver itself stays pure and testable — it never learns that git exists.
|
|
82
|
+
*/
|
|
83
|
+
export function resolveLatestRefs(sources: DuxtSource[]): DuxtSource[] {
|
|
84
|
+
return sources.map((source) => {
|
|
85
|
+
if (!source.refs?.some(isLatestRef)) return source;
|
|
86
|
+
|
|
87
|
+
const newest = newestTag(tagsOf(source.repo));
|
|
88
|
+
|
|
89
|
+
if (!newest) {
|
|
90
|
+
throw new Error(
|
|
91
|
+
`duxt: refs: ['latest'] on ${source.repo ?? 'this repository'} found ` +
|
|
92
|
+
'no tags to choose from. Name a tag explicitly, or drop the entry.'
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return {
|
|
97
|
+
...source,
|
|
98
|
+
refs: source.refs.map((ref) =>
|
|
99
|
+
isLatestRef(ref)
|
|
100
|
+
? {
|
|
101
|
+
tag: newest,
|
|
102
|
+
// The URL keeps saying `latest`, so a bookmark survives the next
|
|
103
|
+
// release; only the ref underneath moves. `label` is what the
|
|
104
|
+
// switcher and the prefix are built from.
|
|
105
|
+
label:
|
|
106
|
+
typeof ref === 'object' ? (ref.label ?? 'latest') : 'latest',
|
|
107
|
+
status: typeof ref === 'object' ? ref.status : undefined
|
|
108
|
+
}
|
|
109
|
+
: ref
|
|
110
|
+
)
|
|
111
|
+
};
|
|
112
|
+
});
|
|
113
|
+
}
|