@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,711 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generated sections: an artefact that is not Markdown, published as pages.
|
|
3
|
+
*
|
|
4
|
+
* A source declares `generated: [{ type, path, label }]` beside its docs tree,
|
|
5
|
+
* and each entry becomes an ordinary Content collection at a URL prefix of its
|
|
6
|
+
* own. That is the whole design decision: everything the layer can already do
|
|
7
|
+
* hangs off a collection — search, `llms.txt`, the feed, the sitemap, the
|
|
8
|
+
* redirects, the 404 fallback and the MCP tools all iterate the resolved
|
|
9
|
+
* manifest — so a section that IS a collection inherits them instead of
|
|
10
|
+
* rebuilding them one type at a time.
|
|
11
|
+
*
|
|
12
|
+
* Pure, and separate from `sections.ts` for the reason `sources-resolve.ts` is
|
|
13
|
+
* separate from `sources.ts`: that half imports `@nuxt/content` and reads
|
|
14
|
+
* files, and the browser refuses to follow either out of app code. This half is
|
|
15
|
+
* a list in, a manifest out, which is what `app.config.ts` and the tests read.
|
|
16
|
+
*/
|
|
17
|
+
import type {
|
|
18
|
+
DuxtRef,
|
|
19
|
+
DuxtResolvedSource,
|
|
20
|
+
DuxtSource,
|
|
21
|
+
DuxtSourceStatus,
|
|
22
|
+
DuxtSourcesOptions
|
|
23
|
+
} from './sources-resolve';
|
|
24
|
+
import {
|
|
25
|
+
expandSources,
|
|
26
|
+
identifier,
|
|
27
|
+
resolveSources,
|
|
28
|
+
slugify
|
|
29
|
+
} from './sources-resolve';
|
|
30
|
+
import { changelogSectionType } from './sections-changelog';
|
|
31
|
+
import { openapiSectionType } from './sections-openapi';
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* One artefact beside a source's Markdown, published as pages of the site.
|
|
35
|
+
*
|
|
36
|
+
* A LIST, because a monorepo is the normal case: `release-please-config.json`
|
|
37
|
+
* already declares a `changelog-path` per package, so several artefacts beside
|
|
38
|
+
* one docs tree is ordinary rather than exotic.
|
|
39
|
+
*
|
|
40
|
+
* Not called `sections`: `duxt.sections` is taken for the second navbar row,
|
|
41
|
+
* and two meanings on one public name is a collision the layer pays for later.
|
|
42
|
+
*/
|
|
43
|
+
/**
|
|
44
|
+
* One version of a generated section — the artefact, and what it is called.
|
|
45
|
+
*
|
|
46
|
+
* `path` and `locales` are the declaration's own, overridden per version: a
|
|
47
|
+
* translated artefact is translated per version, because that is the file that
|
|
48
|
+
* exists.
|
|
49
|
+
*/
|
|
50
|
+
export interface DuxtGeneratedSectionVersion {
|
|
51
|
+
/** Shown in the switcher and used in the URL. */
|
|
52
|
+
version: string;
|
|
53
|
+
/** The artefact this version reads; defaults to the declaration's. */
|
|
54
|
+
path?: string;
|
|
55
|
+
/** Per-locale artefacts for this version; defaults to the declaration's. */
|
|
56
|
+
locales?: Record<string, string>;
|
|
57
|
+
/** Lifecycle of this version. See `DuxtSourceStatus`. */
|
|
58
|
+
status?: DuxtSourceStatus;
|
|
59
|
+
/** Served without a version segment. Defaults to the first in the list. */
|
|
60
|
+
default?: boolean;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export interface DuxtGeneratedSection {
|
|
64
|
+
/** The registry key of the type that parses it, e.g. `changelog`. */
|
|
65
|
+
type: string;
|
|
66
|
+
/**
|
|
67
|
+
* The artefact, relative to the SOURCE'S OWN ROOT — the repository root for
|
|
68
|
+
* a source read off disk, the root of the downloaded checkout for one Content
|
|
69
|
+
* clones. One rule in both directions.
|
|
70
|
+
*
|
|
71
|
+
* A path only, never a URL: an arbitrary location would let an artefact be
|
|
72
|
+
* fetched from anywhere and reopen the build-time-network question the
|
|
73
|
+
* `sources` shorthand has already closed.
|
|
74
|
+
*/
|
|
75
|
+
path: string;
|
|
76
|
+
/**
|
|
77
|
+
* The navbar entry's label, and — slugified — its URL segment.
|
|
78
|
+
*
|
|
79
|
+
* A plain string rather than a `DuxtText`, for the reason a version's label
|
|
80
|
+
* is one: a translated text is not a stable URL. Same pair as `refs`.
|
|
81
|
+
*/
|
|
82
|
+
label: string;
|
|
83
|
+
/** URL segment for this section; defaults to the slugified label. */
|
|
84
|
+
slug?: string;
|
|
85
|
+
/**
|
|
86
|
+
* The versions of THIS ARTEFACT, where the source's own refs do not name
|
|
87
|
+
* them.
|
|
88
|
+
*
|
|
89
|
+
* A version is normally a checkout: list `refs` on the source and every
|
|
90
|
+
* section it carries is published once per ref. An API is usually not kept
|
|
91
|
+
* that way — `openapi/v1.yaml` sits beside `openapi/v2.yaml` in one
|
|
92
|
+
* repository, versioned by FILE — and without this key the two are two
|
|
93
|
+
* unrelated sections with two navbar entries.
|
|
94
|
+
*
|
|
95
|
+
* Declared HERE rather than as two sources, and that is the whole design: two
|
|
96
|
+
* sections are offered as versions of one another only when they came from
|
|
97
|
+
* one declaration (see `DuxtGeneratedMeta.declaration`), and a source always
|
|
98
|
+
* publishes a documentation tree, so a source per API version would publish
|
|
99
|
+
* the prose twice. One declaration, several versions, one artefact.
|
|
100
|
+
*
|
|
101
|
+
* Never beside a versioned SOURCE: the two would each want the same segment
|
|
102
|
+
* of the URL, and the resolver says so rather than nesting them.
|
|
103
|
+
*/
|
|
104
|
+
versions?: DuxtGeneratedSectionVersion[];
|
|
105
|
+
/**
|
|
106
|
+
* The knobs THIS TYPE offers, as the site turned them.
|
|
107
|
+
*
|
|
108
|
+
* Opaque to the scaffold on purpose: a granularity means something to a
|
|
109
|
+
* changelog and nothing to an API reference, and a scaffold that knew the
|
|
110
|
+
* difference would have to grow a field per type. So it is carried, not
|
|
111
|
+
* read — the type validates its own and says so in its own words.
|
|
112
|
+
*/
|
|
113
|
+
options?: DuxtSectionOptions;
|
|
114
|
+
/**
|
|
115
|
+
* The artefact a LOCALE reads, where that locale ships one of its own.
|
|
116
|
+
*
|
|
117
|
+
* Only a `per-locale` type reads this — a `changelog` is written once by the
|
|
118
|
+
* release tool and says so through the translation banner instead. Keyed by
|
|
119
|
+
* the locale code the source declares, with the language alone accepted as
|
|
120
|
+
* well, so `pt` answers for `pt-BR`.
|
|
121
|
+
*
|
|
122
|
+
* A locale absent from this map builds NO collection of its own, which is
|
|
123
|
+
* the point: the existing `fallbackLocale` chain then resolves it to the
|
|
124
|
+
* default language's section and `DuxtTranslationBanner` says the reader is
|
|
125
|
+
* looking at the original. Declaring an artefact for every locale and
|
|
126
|
+
* quietly serving the same file under each would leave that banner silent
|
|
127
|
+
* and the reader unaware.
|
|
128
|
+
*/
|
|
129
|
+
locales?: Record<string, string>;
|
|
130
|
+
/**
|
|
131
|
+
* Where its navbar entry goes.
|
|
132
|
+
*
|
|
133
|
+
* `sections` is the second navbar row — the existing "top-level parts of the
|
|
134
|
+
* documentation" — and the default, so a declared section is findable
|
|
135
|
+
* without further config. `navigation` is the narrow first row, which stays
|
|
136
|
+
* a deliberate choice. `false` puts it nowhere and leaves the site to link
|
|
137
|
+
* it.
|
|
138
|
+
*/
|
|
139
|
+
navigation?: DuxtSectionPlacement;
|
|
140
|
+
/** Icon for that entry; falls back to the type's own. */
|
|
141
|
+
icon?: string;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
export type DuxtSectionPlacement = 'navigation' | 'sections' | false;
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* What a declaration hands its own type, and nothing else reads.
|
|
148
|
+
*
|
|
149
|
+
* A bag rather than a union of the known shapes: the registry is open, so the
|
|
150
|
+
* set of types is not the layer's to enumerate — a consumer's own type gets the
|
|
151
|
+
* same seam the built-in ones use.
|
|
152
|
+
*/
|
|
153
|
+
export type DuxtSectionOptions = Record<string, unknown>;
|
|
154
|
+
|
|
155
|
+
/** One page a type's parser produced out of an artefact. */
|
|
156
|
+
export interface DuxtSectionPage {
|
|
157
|
+
/**
|
|
158
|
+
* File name inside the section, extension included.
|
|
159
|
+
*
|
|
160
|
+
* Content reads the name the way it reads any other: a `NN.` prefix orders
|
|
161
|
+
* the page and is stripped from the URL, so a changelog can be newest-first
|
|
162
|
+
* without the order leaking into the address.
|
|
163
|
+
*/
|
|
164
|
+
file: string;
|
|
165
|
+
/** The whole file — frontmatter block and body. */
|
|
166
|
+
body: string;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/** What the parser is told about the section it is filling. */
|
|
170
|
+
export interface DuxtSectionContext {
|
|
171
|
+
/** The declared label, which is also the index page's title. */
|
|
172
|
+
label: string;
|
|
173
|
+
/** The URL prefix its pages are served at, e.g. `/releases`. */
|
|
174
|
+
prefix: string;
|
|
175
|
+
/** The declaration's own `options`, empty where it named none. */
|
|
176
|
+
options: DuxtSectionOptions;
|
|
177
|
+
/**
|
|
178
|
+
* What the type could not make sense of, but carried on past.
|
|
179
|
+
*
|
|
180
|
+
* NOT `console.warn`, which is what both built-in types did: an artefact is
|
|
181
|
+
* parsed while the config is being loaded, so a warning printed there has
|
|
182
|
+
* scrolled away long before anyone looks — the same failure the Checks panel
|
|
183
|
+
* exists to fix. Collected here instead, the finding reaches
|
|
184
|
+
* `DuxtSectionReport` and from there the ONE report this layer has: the build
|
|
185
|
+
* validator prints it and the Checks panel keeps it.
|
|
186
|
+
*
|
|
187
|
+
* For findings the parser survives. What it cannot read at all it throws on,
|
|
188
|
+
* and a section that produced no pages is reported by the scaffold rather
|
|
189
|
+
* than by the type — neither needs this.
|
|
190
|
+
*
|
|
191
|
+
* Optional, because a type is called with a bare context in a test.
|
|
192
|
+
*/
|
|
193
|
+
warn?: (message: string) => void;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* What reading one artefact said about it, for the report.
|
|
198
|
+
*
|
|
199
|
+
* On the manifest entry rather than in a registry of its own, and that is the
|
|
200
|
+
* same decision `duxtSectionTypes` documents above: `content.config.ts` and the
|
|
201
|
+
* duxt module are loaded through two different loaders, so a module-level
|
|
202
|
+
* collector written by one of them is invisible to the other. A field on the
|
|
203
|
+
* entry travels with the manifest that is already handed around — into
|
|
204
|
+
* `app.config`, which is how the Checks panel sees it at all.
|
|
205
|
+
*/
|
|
206
|
+
export interface DuxtSectionReport {
|
|
207
|
+
/** How many pages the type produced. */
|
|
208
|
+
pages: number;
|
|
209
|
+
/** What the type carried on past — see `DuxtSectionContext.warn`. */
|
|
210
|
+
warnings: string[];
|
|
211
|
+
/** Set when the declared artefact was not there to read. */
|
|
212
|
+
missing?: boolean;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* A type in the registry: what turns one artefact into pages.
|
|
217
|
+
*
|
|
218
|
+
* The whole extension surface, and public from day one. While the package is
|
|
219
|
+
* `0.x`, `bump-minor-pre-major` makes a `feat!:` a minor bump, so correcting
|
|
220
|
+
* this shape costs no major release — the promise is reviewed once before
|
|
221
|
+
* `1.0.0`, which is when it stops being cheap.
|
|
222
|
+
*/
|
|
223
|
+
export interface DuxtSectionType {
|
|
224
|
+
/** The artefact, split into pages carrying their own frontmatter. */
|
|
225
|
+
parse: (artefact: string, context: DuxtSectionContext) => DuxtSectionPage[];
|
|
226
|
+
/**
|
|
227
|
+
* How this type behaves against the one-collection-per-version mechanic.
|
|
228
|
+
*
|
|
229
|
+
* `global` is one history read from the default version and served at a
|
|
230
|
+
* version-neutral URL, with the switcher suppressed — what a changelog is.
|
|
231
|
+
* `per-version` is a section per version, like any other page.
|
|
232
|
+
*/
|
|
233
|
+
versioning: 'global' | 'per-version';
|
|
234
|
+
/**
|
|
235
|
+
* What a localised site shows when the artefact has one language.
|
|
236
|
+
*
|
|
237
|
+
* `original` builds ONE collection from the default locale and lets the
|
|
238
|
+
* existing translation banner say so — a release log is written once by the
|
|
239
|
+
* release tool, and there is nothing per-locale about it.
|
|
240
|
+
*
|
|
241
|
+
* `per-locale` follows the source's own languages, as far as the declaration
|
|
242
|
+
* reaches: a collection for the default language, and one for every locale
|
|
243
|
+
* `DuxtGeneratedSection.locales` names an artefact for. A locale it does not
|
|
244
|
+
* name builds NOTHING rather than a copy of the original — which is what
|
|
245
|
+
* leaves `sourcesForRoute` free to fall through to the default entry and
|
|
246
|
+
* `DuxtTranslationBanner` free to say the reader is looking at the original.
|
|
247
|
+
*/
|
|
248
|
+
localisation: 'original' | 'per-locale';
|
|
249
|
+
/**
|
|
250
|
+
* The layout its pages render in, through the shared slot.
|
|
251
|
+
*
|
|
252
|
+
* A layout name a type binds is PUBLIC SURFACE — renaming one later is a
|
|
253
|
+
* `feat!:`. Unset renders the section in the ordinary docs chrome.
|
|
254
|
+
*
|
|
255
|
+
* A FUNCTION where the declaration's own options decide: the same type can
|
|
256
|
+
* produce two different things, and a changelog is the case that proves it —
|
|
257
|
+
* split into a page per release it is a timeline with chrome of its own,
|
|
258
|
+
* rendered as the one file it was written as it is an ordinary docs page and
|
|
259
|
+
* wants the sidebar, the breadcrumb and the table of contents back.
|
|
260
|
+
*/
|
|
261
|
+
layout?: string | ((options: DuxtSectionOptions) => string | undefined);
|
|
262
|
+
/** Icon for the navbar entry, when the declaration names none. */
|
|
263
|
+
icon?: string;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/** The registry: type name to type. */
|
|
267
|
+
export type DuxtSectionTypes = Record<string, DuxtSectionType>;
|
|
268
|
+
|
|
269
|
+
/** The types the layer ships. */
|
|
270
|
+
export const duxtBuiltinSectionTypes: DuxtSectionTypes = {
|
|
271
|
+
changelog: changelogSectionType,
|
|
272
|
+
openapi: openapiSectionType
|
|
273
|
+
};
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* The registry, open for a consumer to add to.
|
|
277
|
+
*
|
|
278
|
+
* A merge rather than a mutable global: `content.config.ts` and the duxt module
|
|
279
|
+
* are loaded through two different loaders, and a registry each of them wrote
|
|
280
|
+
* into would be two registries. Handing the map in keeps both halves reading
|
|
281
|
+
* the one the site declared — the same reason `sources` is data rather than a
|
|
282
|
+
* hook. A consumer's entry wins, so a type can also be replaced.
|
|
283
|
+
*/
|
|
284
|
+
export function duxtSectionTypes(custom?: DuxtSectionTypes): DuxtSectionTypes {
|
|
285
|
+
return { ...duxtBuiltinSectionTypes, ...custom };
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* What a generated collection carries beyond an ordinary source.
|
|
290
|
+
*
|
|
291
|
+
* Lives under one key rather than as loose fields, so `source.generated` is the
|
|
292
|
+
* single question anything downstream has to ask — the version switcher, the
|
|
293
|
+
* layout slot and the "edit this page" link each ask it once.
|
|
294
|
+
*/
|
|
295
|
+
export interface DuxtGeneratedMeta {
|
|
296
|
+
/** The registry key the artefact was parsed with. */
|
|
297
|
+
type: string;
|
|
298
|
+
/** The navbar label, as declared. */
|
|
299
|
+
label: string;
|
|
300
|
+
/** The URL segment, as resolved. */
|
|
301
|
+
slug: string;
|
|
302
|
+
/**
|
|
303
|
+
* WHICH DECLARATION produced this entry — the identity, not an address.
|
|
304
|
+
*
|
|
305
|
+
* A section is one collection per version and per locale, so a single
|
|
306
|
+
* `generated: [...]` entry reaches the manifest as several sources, and the
|
|
307
|
+
* navbar has to put ONE link in the row for all of them. Which of them belong
|
|
308
|
+
* together is known exactly here, while `source.generated` is being iterated,
|
|
309
|
+
* and nowhere else afterwards — so it is recorded rather than reconstructed.
|
|
310
|
+
*
|
|
311
|
+
* It was reconstructed, from `(slug, repository)`, and that guess is wrong in
|
|
312
|
+
* both directions: two sources declaring a `Releases` section — per-package
|
|
313
|
+
* docs trees in a monorepo, the shape this feature calls the normal case —
|
|
314
|
+
* read as one declaration and lost an entry from the row, while nothing in
|
|
315
|
+
* the resolver ever promised the pair was unique. Prefixes are claimed per
|
|
316
|
+
* `(locale, prefix)`, so those two sections are not a collision and no error
|
|
317
|
+
* is raised.
|
|
318
|
+
*
|
|
319
|
+
* Its VALUE means nothing: a counter over the declarations in the site's own
|
|
320
|
+
* source list, only ever compared for equality inside one manifest.
|
|
321
|
+
*/
|
|
322
|
+
declaration: number;
|
|
323
|
+
/** Where its navbar entry goes. */
|
|
324
|
+
navigation: DuxtSectionPlacement;
|
|
325
|
+
/** Icon for that entry. */
|
|
326
|
+
icon?: string;
|
|
327
|
+
/** The layout its pages render in, when the type names one for these options. */
|
|
328
|
+
layout?: string;
|
|
329
|
+
/** The declaration's own options, where it named any. */
|
|
330
|
+
options?: DuxtSectionOptions;
|
|
331
|
+
/** The type's versioning policy — `global` suppresses the switcher. */
|
|
332
|
+
versioning: 'global' | 'per-version';
|
|
333
|
+
/** The type's localisation policy. */
|
|
334
|
+
localisation: 'original' | 'per-locale';
|
|
335
|
+
/** Whether Content downloads the repository this artefact sits in. */
|
|
336
|
+
remote: boolean;
|
|
337
|
+
/**
|
|
338
|
+
* What reading the artefact said about it — see `DuxtSectionReport`.
|
|
339
|
+
*
|
|
340
|
+
* Absent until something has actually read it, which is not the resolver's
|
|
341
|
+
* job: `resolveGeneratedSections` is pure and never touches disk. It is
|
|
342
|
+
* filled by whoever reads the file — `sections.ts` while the collections are
|
|
343
|
+
* being declared, and `readSectionReports` for the manifest the modules hold.
|
|
344
|
+
* Absent therefore means "not read here", never "nothing to report", and the
|
|
345
|
+
* checks are written to say nothing rather than guess.
|
|
346
|
+
*/
|
|
347
|
+
report?: DuxtSectionReport;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
/**
|
|
351
|
+
* The generated sections a source list declares, as manifest entries.
|
|
352
|
+
*
|
|
353
|
+
* They are ORDINARY resolved sources with one extra key, because that is what
|
|
354
|
+
* makes the rest of the layer work on them unchanged: `sourcesForRoute` picks
|
|
355
|
+
* the longest matching prefix, so `/releases` beats the docs tree at `/` with
|
|
356
|
+
* no special case anywhere.
|
|
357
|
+
*/
|
|
358
|
+
export function resolveGeneratedSections(
|
|
359
|
+
sources: DuxtSource[],
|
|
360
|
+
options: DuxtSourcesOptions = {},
|
|
361
|
+
types: DuxtSectionTypes = duxtSectionTypes()
|
|
362
|
+
): DuxtResolvedSource[] {
|
|
363
|
+
const resolved = resolveSources(sources, options);
|
|
364
|
+
const expanded = expandSources(sources, options);
|
|
365
|
+
const generated: DuxtResolvedSource[] = [];
|
|
366
|
+
|
|
367
|
+
// Claimed per locale, exactly as `resolveSources` claims its own prefixes:
|
|
368
|
+
// two languages serving one prefix is not a collision, it is the point.
|
|
369
|
+
const taken = new Map<string, string>(
|
|
370
|
+
resolved.map((entry) => [
|
|
371
|
+
`${entry.locale ?? ''}|${entry.prefix}`,
|
|
372
|
+
`the documentation at "${entry.prefix || '/'}"`
|
|
373
|
+
])
|
|
374
|
+
);
|
|
375
|
+
|
|
376
|
+
// Counted over the whole list rather than per source, so the identity is
|
|
377
|
+
// unique across the manifest without anything having to pair it with a source
|
|
378
|
+
// again. Incremented for every declaration, including one that resolves to no
|
|
379
|
+
// entry at all: it names a declaration, not a row of the output.
|
|
380
|
+
let declarations = 0;
|
|
381
|
+
|
|
382
|
+
sources.forEach((source) => {
|
|
383
|
+
for (const declared of source.generated ?? []) {
|
|
384
|
+
const declaration = declarations++;
|
|
385
|
+
const type = types[declared.type];
|
|
386
|
+
|
|
387
|
+
if (!type) {
|
|
388
|
+
// Named rather than ignored: an unknown type is a typo in the site's
|
|
389
|
+
// own config, and a section silently absent is the failure this layer
|
|
390
|
+
// exists to stop.
|
|
391
|
+
throw new Error(
|
|
392
|
+
`duxt: the generated section "${declared.label}" asks for the type ` +
|
|
393
|
+
`"${declared.type}", which no type in the registry answers to ` +
|
|
394
|
+
`(${Object.keys(types).sort().join(', ') || 'none'}). ` +
|
|
395
|
+
'Register it in `duxt.sectionTypes`.'
|
|
396
|
+
);
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
const options = declared.options ?? {};
|
|
400
|
+
|
|
401
|
+
// LOWERCASED, and that is not a style choice. Content slugifies a page's
|
|
402
|
+
// own path with `lower: true`, so a section called `Releases` would be
|
|
403
|
+
// served at `/releases` while the manifest said `/Releases` — and every
|
|
404
|
+
// lookup that matches a route against a prefix would miss.
|
|
405
|
+
const slug = slugify(declared.slug ?? declared.label).toLowerCase();
|
|
406
|
+
|
|
407
|
+
if (!slug) {
|
|
408
|
+
throw new Error(
|
|
409
|
+
`duxt: the generated section "${declared.label}" resolves to an ` +
|
|
410
|
+
'empty URL segment. Give it a `slug`.'
|
|
411
|
+
);
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
// One pass where the declaration names no versions, so a section that
|
|
415
|
+
// has none resolves exactly as it did.
|
|
416
|
+
const editions: (DuxtGeneratedSectionVersion | undefined)[] = declared
|
|
417
|
+
.versions?.length
|
|
418
|
+
? declared.versions
|
|
419
|
+
: [undefined];
|
|
420
|
+
|
|
421
|
+
for (const base of basesFor(source, resolved, expanded, type, declared)) {
|
|
422
|
+
for (const [index, edition] of editions.entries()) {
|
|
423
|
+
if (edition && base.entry.version) {
|
|
424
|
+
throw new Error(
|
|
425
|
+
`duxt: the generated section "${declared.label}" names its own ` +
|
|
426
|
+
'versions and sits on a source that is versioned by refs. ' +
|
|
427
|
+
'Both want the same segment of the URL — put the versions on ' +
|
|
428
|
+
'one of the two.'
|
|
429
|
+
);
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
// A version-NEUTRAL type has one history at one URL, so a list of
|
|
433
|
+
// versions is a contradiction rather than a shape to resolve.
|
|
434
|
+
if (edition && type.versioning === 'global') {
|
|
435
|
+
throw new Error(
|
|
436
|
+
`duxt: the generated section "${declared.label}" names versions, ` +
|
|
437
|
+
`but the type "${declared.type}" publishes one history at a ` +
|
|
438
|
+
'version-neutral URL.'
|
|
439
|
+
);
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
const editionDefault =
|
|
443
|
+
editions.length === 1 ||
|
|
444
|
+
(edition?.default ??
|
|
445
|
+
(!editions.some((entry) => entry?.default) && index === 0));
|
|
446
|
+
|
|
447
|
+
const version = edition ? slugify(edition.version) : undefined;
|
|
448
|
+
|
|
449
|
+
const segments = version && !editionDefault ? `/${version}` : '';
|
|
450
|
+
const artefact = edition
|
|
451
|
+
? (artefactFor({ ...declared, ...edition }, base.entry) ??
|
|
452
|
+
base.path)
|
|
453
|
+
: base.path;
|
|
454
|
+
|
|
455
|
+
const prefix = `${base.entry.prefix}${segments}/${slug}`;
|
|
456
|
+
const claim = `${base.entry.locale ?? ''}|${prefix}`;
|
|
457
|
+
const previous = taken.get(claim);
|
|
458
|
+
|
|
459
|
+
if (previous) {
|
|
460
|
+
throw new Error(
|
|
461
|
+
`duxt: the generated section "${declared.label}" resolves to the ` +
|
|
462
|
+
`URL prefix "${prefix}", which ${previous} already claims. ` +
|
|
463
|
+
'Give the section a `slug`.'
|
|
464
|
+
);
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
taken.set(claim, `the generated section "${declared.label}"`);
|
|
468
|
+
|
|
469
|
+
generated.push({
|
|
470
|
+
// THE VERSION IS PART OF THE NAME where the declaration owns it.
|
|
471
|
+
// A ref-versioned source hands each version its own base entry, so
|
|
472
|
+
// the base's name already differs; a declaration's versions share
|
|
473
|
+
// one base, and without this both editions claimed one collection —
|
|
474
|
+
// the second overwrote the first in `content.config.ts`, and the
|
|
475
|
+
// default version 404'd while the older one rendered.
|
|
476
|
+
collection: `${base.entry.collection}_${identifier(slug)}${
|
|
477
|
+
version ? `_${identifier(version)}` : ''
|
|
478
|
+
}`,
|
|
479
|
+
prefix,
|
|
480
|
+
repo: base.entry.repo,
|
|
481
|
+
// `global` is version-NEUTRAL, so it carries no version at all: the
|
|
482
|
+
// switcher lists what has a version, and a changelog must not be
|
|
483
|
+
// offered as one of the versions of the documentation beside it.
|
|
484
|
+
version:
|
|
485
|
+
type.versioning === 'per-version'
|
|
486
|
+
? (version ?? base.entry.version)
|
|
487
|
+
: undefined,
|
|
488
|
+
// FOLLOWS THE VERSION IT WAS READ AT, where there is one. This was
|
|
489
|
+
// hard-wired to `true`, which is right for a `global` section — one
|
|
490
|
+
// entry, served at a URL with no version in it — and wrong for every
|
|
491
|
+
// other: it made the v1.9 reference claim to be the default as
|
|
492
|
+
// loudly as the v2 one. Two things read that claim and both got it
|
|
493
|
+
// wrong. `excludeOldVersionsFromSitemap` hides what is not the
|
|
494
|
+
// default, so a deprecated reference stayed in the sitemap while the
|
|
495
|
+
// deprecated documentation beside it was excluded; and the version
|
|
496
|
+
// switcher captions the default, so it offered two of them.
|
|
497
|
+
isDefault:
|
|
498
|
+
type.versioning === 'per-version'
|
|
499
|
+
? base.entry.isDefault && editionDefault
|
|
500
|
+
: true,
|
|
501
|
+
repository: base.entry.repository,
|
|
502
|
+
repositoryUrl: base.entry.repositoryUrl,
|
|
503
|
+
ref: base.entry.ref,
|
|
504
|
+
refKind: base.entry.refKind,
|
|
505
|
+
// The ARTEFACT, not a folder: `DuxtPageInfo` links back to the file a
|
|
506
|
+
// page was written in, and for a generated section every page in it
|
|
507
|
+
// was written in this one. Per LOCALE where the declaration names one
|
|
508
|
+
// — see `artefactFor`.
|
|
509
|
+
path: artefact,
|
|
510
|
+
locale: base.entry.locale,
|
|
511
|
+
isDefaultLocale: base.entry.isDefaultLocale,
|
|
512
|
+
status: edition?.status ?? base.entry.status,
|
|
513
|
+
// Never read for history. Its pages have no file of their own on
|
|
514
|
+
// disk, so `git log` would answer about nothing; a type that has
|
|
515
|
+
// dates puts them in the frontmatter it writes.
|
|
516
|
+
history: false,
|
|
517
|
+
generated: {
|
|
518
|
+
type: declared.type,
|
|
519
|
+
label: declared.label,
|
|
520
|
+
slug,
|
|
521
|
+
declaration,
|
|
522
|
+
navigation: declared.navigation ?? 'sections',
|
|
523
|
+
icon: declared.icon ?? type.icon,
|
|
524
|
+
layout:
|
|
525
|
+
typeof type.layout === 'function'
|
|
526
|
+
? type.layout(options)
|
|
527
|
+
: type.layout,
|
|
528
|
+
versioning: type.versioning,
|
|
529
|
+
localisation: type.localisation,
|
|
530
|
+
remote: base.remote,
|
|
531
|
+
// Only when the site named some: an empty object where there was
|
|
532
|
+
// `undefined` is a different value in the manifest every page
|
|
533
|
+
// ships, and this one changes on no site that declares nothing.
|
|
534
|
+
...(Object.keys(options).length ? { options } : {})
|
|
535
|
+
}
|
|
536
|
+
});
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
});
|
|
541
|
+
|
|
542
|
+
return generated;
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
/**
|
|
546
|
+
* Which resolved entries a section hangs off, per the type's own policies.
|
|
547
|
+
*
|
|
548
|
+
* Making versioning and localisation POLICIES is what turns the sharpest
|
|
549
|
+
* difference between two types into a parameter instead of a fork: a changelog
|
|
550
|
+
* is one global history, an API reference genuinely is per version, and both
|
|
551
|
+
* are then ordinary answers rather than special cases.
|
|
552
|
+
*/
|
|
553
|
+
function basesFor(
|
|
554
|
+
source: DuxtSource,
|
|
555
|
+
resolved: DuxtResolvedSource[],
|
|
556
|
+
expanded: ReturnType<typeof expandSources>,
|
|
557
|
+
type: DuxtSectionType,
|
|
558
|
+
declared: DuxtGeneratedSection
|
|
559
|
+
): { entry: DuxtResolvedSource; path: string; remote: boolean }[] {
|
|
560
|
+
const bases: { entry: DuxtResolvedSource; path: string; remote: boolean }[] =
|
|
561
|
+
[];
|
|
562
|
+
|
|
563
|
+
resolved.forEach((entry, index) => {
|
|
564
|
+
const combination = expanded[index]!;
|
|
565
|
+
if (combination.source !== source) return;
|
|
566
|
+
|
|
567
|
+
if (type.localisation === 'original' && !entry.isDefaultLocale) return;
|
|
568
|
+
if (type.versioning === 'global' && !entry.isDefault) return;
|
|
569
|
+
|
|
570
|
+
const path = artefactFor(declared, entry);
|
|
571
|
+
if (!path) return;
|
|
572
|
+
|
|
573
|
+
bases.push({ entry, path, remote: Boolean(combination.effective.repo) });
|
|
574
|
+
});
|
|
575
|
+
|
|
576
|
+
return bases;
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
/**
|
|
580
|
+
* The artefact one entry reads, or nothing where it reads none.
|
|
581
|
+
*
|
|
582
|
+
* The default language always reads the declared `path`; every other language
|
|
583
|
+
* reads what `locales` names for it, and builds no collection at all when that
|
|
584
|
+
* map is silent — see `DuxtGeneratedSection.locales` for why the silence is the
|
|
585
|
+
* useful answer rather than a gap to paper over.
|
|
586
|
+
*/
|
|
587
|
+
function artefactFor(
|
|
588
|
+
declared: DuxtGeneratedSection,
|
|
589
|
+
entry: DuxtResolvedSource
|
|
590
|
+
): string | undefined {
|
|
591
|
+
if (entry.isDefaultLocale || !entry.locale) return declared.path;
|
|
592
|
+
|
|
593
|
+
const locales = declared.locales ?? {};
|
|
594
|
+
|
|
595
|
+
// The language alone answers for a region, exactly as the locale FILES do:
|
|
596
|
+
// one `pt` artefact serves `pt-PT` and `pt-BR`.
|
|
597
|
+
return locales[entry.locale] ?? locales[entry.locale.split('-')[0]!];
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
/**
|
|
601
|
+
* The ref an entry was read at, for the half of the build that downloads it.
|
|
602
|
+
*
|
|
603
|
+
* `DuxtResolvedSource` keeps a ref by NAME and a kind beside it, because that
|
|
604
|
+
* is what a link back to the file needs; Content wants the pair as a `branch`
|
|
605
|
+
* or a `tag` again. Rebuilding it here keeps that translation in one place.
|
|
606
|
+
*/
|
|
607
|
+
export function generatedSectionRef(
|
|
608
|
+
entry: DuxtResolvedSource
|
|
609
|
+
): DuxtRef | undefined {
|
|
610
|
+
if (!entry.ref) return undefined;
|
|
611
|
+
|
|
612
|
+
return entry.refKind === 'tag' ? { tag: entry.ref } : { branch: entry.ref };
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
/**
|
|
616
|
+
* The artefact, as its type reads it.
|
|
617
|
+
*
|
|
618
|
+
* Here rather than beside the file reading, so the SEVERITY POLICY is testable:
|
|
619
|
+
* whether a finding fails the build or warns is one of this feature's stated
|
|
620
|
+
* decisions, and it was decided per source kind rather than per finding. The
|
|
621
|
+
* reading itself stays in `sections.ts`, which is the half that touches disk.
|
|
622
|
+
*
|
|
623
|
+
* A type that produces nothing out of a file that exists is the same finding as
|
|
624
|
+
* a file that is not there, and carries the same severity — the section would
|
|
625
|
+
* otherwise be an empty collection, which is a 404 on every URL it claims and
|
|
626
|
+
* nothing said about why.
|
|
627
|
+
*/
|
|
628
|
+
export function sectionPages(
|
|
629
|
+
entry: DuxtResolvedSource,
|
|
630
|
+
type: DuxtSectionType,
|
|
631
|
+
artefact: string
|
|
632
|
+
): DuxtSectionPage[] {
|
|
633
|
+
const warnings: string[] = [];
|
|
634
|
+
|
|
635
|
+
const pages = type.parse(artefact, {
|
|
636
|
+
label: entry.generated!.label,
|
|
637
|
+
prefix: entry.prefix,
|
|
638
|
+
options: entry.generated!.options ?? {},
|
|
639
|
+
warn: (message) => {
|
|
640
|
+
// Deduplicated here rather than in each type: one unresolvable `$ref` is
|
|
641
|
+
// reached from every operation that uses it, and a report that says the
|
|
642
|
+
// same sentence forty times is a report nobody reads to the end.
|
|
643
|
+
if (!warnings.includes(message)) warnings.push(message);
|
|
644
|
+
}
|
|
645
|
+
});
|
|
646
|
+
|
|
647
|
+
entry.generated!.report = { pages: pages.length, warnings };
|
|
648
|
+
|
|
649
|
+
// An EMPTY section is not reported here any more, in either direction beyond
|
|
650
|
+
// the local throw. The report above says `pages: 0`, and `validate-report.ts`
|
|
651
|
+
// turns that into a finding at the severity the source's kind asks for —
|
|
652
|
+
// which is where every other finding in this layer already lives. A
|
|
653
|
+
// `console.warn` beside it was a second reporting channel that scrolled away.
|
|
654
|
+
if (!pages.length && !entry.generated!.remote) {
|
|
655
|
+
throw new Error(
|
|
656
|
+
`duxt: ${entry.path} holds nothing the "${entry.generated!.type}" type ` +
|
|
657
|
+
`can read, so the section "${entry.generated!.label}" has no pages.`
|
|
658
|
+
);
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
return pages;
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
/**
|
|
665
|
+
* A declared artefact that is not there.
|
|
666
|
+
*
|
|
667
|
+
* The severity is not uniform, and follows the rule `modules/validate.ts`
|
|
668
|
+
* already states. A LOCAL source is the site's own configuration, so a path
|
|
669
|
+
* that does not exist is a mistake in it and fails the build. A REMOTE one may
|
|
670
|
+
* legitimately not have had the file at an older tag — a remote source can go
|
|
671
|
+
* stale between releases without that being this build's fault — so it is
|
|
672
|
+
* recorded as a finding, and the section is simply not built.
|
|
673
|
+
*
|
|
674
|
+
* Returns the pages a caller should carry on with, which for the recorded case
|
|
675
|
+
* is none — so the two severities read as one expression at both call sites.
|
|
676
|
+
*/
|
|
677
|
+
export function missingSectionArtefact(
|
|
678
|
+
entry: DuxtResolvedSource,
|
|
679
|
+
file: string
|
|
680
|
+
): DuxtSectionPage[] {
|
|
681
|
+
entry.generated!.report = { pages: 0, warnings: [], missing: true };
|
|
682
|
+
|
|
683
|
+
// Remote: recorded, not printed. `validate-report.ts` reads the report and
|
|
684
|
+
// says it once, in the same list as every other finding — see `sectionPages`.
|
|
685
|
+
if (entry.generated!.remote) return [];
|
|
686
|
+
|
|
687
|
+
throw new Error(
|
|
688
|
+
`duxt: the generated section "${entry.generated!.label}" declares ` +
|
|
689
|
+
`${entry.path}, which this repository does not have (looked in ${file}). ` +
|
|
690
|
+
"A generated section resolves its path against the source's own root."
|
|
691
|
+
);
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
/**
|
|
695
|
+
* The whole manifest: the documentation, then whatever it generates beside it.
|
|
696
|
+
*
|
|
697
|
+
* `duxtSourceManifest` deliberately stays the documentation half alone —
|
|
698
|
+
* `duxtSources` walks it index-for-index against `expandSources`, and an entry
|
|
699
|
+
* appended there would put the two out of step. Everything that reads the
|
|
700
|
+
* manifest to serve a site reads this one.
|
|
701
|
+
*/
|
|
702
|
+
export function duxtManifest(
|
|
703
|
+
sources: DuxtSource[],
|
|
704
|
+
options: DuxtSourcesOptions = {},
|
|
705
|
+
types: DuxtSectionTypes = duxtSectionTypes()
|
|
706
|
+
): DuxtResolvedSource[] {
|
|
707
|
+
return [
|
|
708
|
+
...resolveSources(sources, options),
|
|
709
|
+
...resolveGeneratedSections(sources, options, types)
|
|
710
|
+
];
|
|
711
|
+
}
|