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