livemark 0.25.0 → 0.27.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/README.md +3 -2
- package/build/actions/config/define.d.ts +12 -0
- package/build/actions/config/define.unit.js +8 -1
- package/build/actions/escape/copy.d.ts +11 -0
- package/build/actions/escape/copy.js +29 -0
- package/build/actions/escape/copy.unit.d.ts +1 -0
- package/build/actions/escape/copy.unit.js +37 -0
- package/build/actions/escape/list.d.ts +12 -0
- package/build/actions/escape/list.js +27 -0
- package/build/actions/escape/list.unit.d.ts +1 -0
- package/build/actions/escape/list.unit.js +36 -0
- package/build/actions/target/build.d.ts +9 -26
- package/build/actions/target/build.js +50 -71
- package/build/actions/target/build.unit.d.ts +1 -0
- package/build/actions/target/build.unit.js +76 -0
- package/build/actions/target/clear.d.ts +4 -0
- package/build/actions/target/clear.js +14 -0
- package/build/actions/target/clear.unit.d.ts +1 -0
- package/build/actions/target/clear.unit.js +23 -0
- package/build/commands/build.js +1 -2
- package/build/commands/clear.d.ts +5 -0
- package/build/commands/clear.js +15 -0
- package/build/commands/escape.d.ts +4 -3
- package/build/commands/escape.js +45 -48
- package/build/commands/preview.js +1 -2
- package/build/commands/start.js +1 -2
- package/build/components/Abbr.d.ts +1 -1
- package/build/components/Abbr.js +2 -4
- package/build/components/AnsiCode.d.ts +1 -1
- package/build/components/AnsiCode.js +2 -2
- package/build/components/Article.d.ts +2 -1
- package/build/components/Article.js +7 -14
- package/build/components/BackToTop.d.ts +1 -1
- package/build/components/Banner.d.ts +2 -2
- package/build/components/Banner.js +7 -3
- package/build/components/BlogIndex.d.ts +1 -1
- package/build/components/BlogIndex.js +2 -11
- package/build/components/Breadcrumbs.d.ts +1 -1
- package/build/components/Breadcrumbs.js +2 -3
- package/build/components/Callout.d.ts +1 -1
- package/build/components/Callout.js +5 -9
- package/build/components/Card.d.ts +1 -1
- package/build/components/Card.js +2 -3
- package/build/components/Cards.d.ts +1 -1
- package/build/components/ChangelogIndex.d.ts +1 -1
- package/build/components/ChangelogIndex.js +2 -11
- package/build/components/ChoiceList.d.ts +15 -0
- package/build/components/ChoiceList.js +13 -0
- package/build/components/CodeBlock.d.ts +1 -1
- package/build/components/CodeBlock.js +3 -16
- package/build/components/CodeTabs.d.ts +1 -1
- package/build/components/CodeTabs.js +2 -3
- package/build/components/Column.d.ts +1 -1
- package/build/components/Columns.d.ts +1 -1
- package/build/components/Content.d.ts +1 -1
- package/build/components/ContentTab.d.ts +1 -1
- package/build/components/ContentTabs.d.ts +1 -1
- package/build/components/ContentTabs.js +2 -3
- package/build/components/DefaultCatchBoundary.d.ts +1 -1
- package/build/components/DefaultCatchBoundary.js +5 -7
- package/build/components/DefinitionList.d.ts +3 -3
- package/build/components/Details.d.ts +1 -1
- package/build/components/Details.js +2 -5
- package/build/components/FileTree.d.ts +1 -1
- package/build/components/FileTree.js +3 -5
- package/build/components/Footer.d.ts +1 -1
- package/build/components/Footer.js +2 -5
- package/build/components/FootnoteRef.d.ts +1 -1
- package/build/components/FootnoteRef.js +2 -4
- package/build/components/Header.d.ts +6 -0
- package/build/components/Header.js +26 -0
- package/build/components/HeaderMenu.d.ts +2 -0
- package/build/components/HeaderMenu.js +27 -0
- package/build/components/HeaderNav.d.ts +6 -0
- package/build/components/HeaderNav.js +74 -0
- package/build/components/HeaderSearch.d.ts +4 -0
- package/build/components/HeaderSearch.js +33 -0
- package/build/components/Heading.d.ts +6 -6
- package/build/components/InlineBadge.d.ts +2 -2
- package/build/components/InlineBadge.js +1 -1
- package/build/components/InlineIcon.d.ts +1 -1
- package/build/components/InlineToc.d.ts +1 -1
- package/build/components/InlineToc.js +2 -4
- package/build/components/Layout.d.ts +2 -1
- package/build/components/Layout.js +5 -58
- package/build/components/LinkButton.d.ts +2 -2
- package/build/components/LinkButton.js +1 -1
- package/build/components/MenuSheet.d.ts +22 -0
- package/build/components/MenuSheet.js +27 -0
- package/build/components/Mermaid.d.ts +1 -1
- package/build/components/MermaidRenderer.d.ts +1 -1
- package/build/components/MermaidRenderer.js +20 -8
- package/build/components/NotFound.d.ts +1 -1
- package/build/components/NotFound.js +3 -6
- package/build/components/PackageTabs.d.ts +1 -1
- package/build/components/PackageTabs.js +3 -7
- package/build/components/PageToolbar.d.ts +1 -1
- package/build/components/PageToolbar.js +2 -10
- package/build/components/PrevNext.d.ts +1 -1
- package/build/components/PrevNext.js +2 -10
- package/build/components/Root.d.ts +4 -0
- package/build/components/Root.js +8 -0
- package/build/components/Search.d.ts +5 -2
- package/build/components/Search.js +11 -9
- package/build/components/SearchDialog.d.ts +1 -1
- package/build/components/SearchDialog.js +10 -133
- package/build/components/Section.d.ts +1 -1
- package/build/components/SectionHeader.d.ts +1 -1
- package/build/components/SheetPanel.d.ts +12 -0
- package/build/components/SheetPanel.js +19 -0
- package/build/components/Sidebar.d.ts +1 -1
- package/build/components/Sidebar.js +12 -14
- package/build/components/SidebarArticles.d.ts +4 -1
- package/build/components/SidebarArticles.js +5 -9
- package/build/components/SidebarLinks.d.ts +4 -1
- package/build/components/SidebarLinks.js +6 -9
- package/build/components/SidebarPosts.d.ts +4 -1
- package/build/components/SidebarPosts.js +7 -23
- package/build/components/SidebarReleases.d.ts +4 -1
- package/build/components/SidebarReleases.js +5 -15
- package/build/components/SidebarSectionPicker.d.ts +8 -0
- package/build/components/SidebarSectionPicker.js +31 -0
- package/build/components/SidebarSections.d.ts +1 -1
- package/build/components/SidebarSections.js +5 -9
- package/build/components/SiteLink.d.ts +1 -1
- package/build/components/SiteTitle.d.ts +1 -1
- package/build/components/SiteTitle.js +2 -6
- package/build/components/SortableTable.d.ts +1 -1
- package/build/components/SoundCloud.d.ts +1 -1
- package/build/components/SoundCloud.js +2 -2
- package/build/components/TagIndex.d.ts +1 -1
- package/build/components/TagIndex.js +2 -11
- package/build/components/Theme.d.ts +2 -1
- package/build/components/Theme.js +35 -15
- package/build/components/Toc.d.ts +5 -4
- package/build/components/Toc.js +35 -35
- package/build/components/YouTube.d.ts +1 -1
- package/build/components/YouTube.js +2 -2
- package/build/components/ZoomImage.d.ts +1 -1
- package/build/content/article.js +4 -11
- package/build/content-collections.js +6 -4
- package/build/elements/accordion.d.ts +4 -4
- package/build/elements/accordion.js +2 -3
- package/build/elements/alert-dialog.d.ts +12 -12
- package/build/elements/alert-dialog.js +2 -4
- package/build/elements/alert.d.ts +4 -4
- package/build/elements/alert.js +2 -2
- package/build/elements/aspect-ratio.d.ts +1 -1
- package/build/elements/avatar.d.ts +6 -6
- package/build/elements/badge.d.ts +1 -1
- package/build/elements/badge.js +3 -1
- package/build/elements/breadcrumb.d.ts +6 -6
- package/build/elements/breadcrumb.js +2 -4
- package/build/elements/button-group.d.ts +2 -2
- package/build/elements/button-group.js +5 -5
- package/build/elements/button.d.ts +2 -2
- package/build/elements/button.js +9 -7
- package/build/elements/calendar.d.ts +2 -2
- package/build/elements/calendar.js +2 -2
- package/build/elements/card.d.ts +7 -7
- package/build/elements/card.js +4 -4
- package/build/elements/carousel.d.ts +5 -5
- package/build/elements/carousel.js +3 -7
- package/build/elements/chart.d.ts +6 -6
- package/build/elements/chart.js +5 -11
- package/build/elements/checkbox.d.ts +1 -1
- package/build/elements/collapsible.d.ts +3 -3
- package/build/elements/command.d.ts +9 -9
- package/build/elements/command.js +5 -11
- package/build/elements/context-menu.d.ts +15 -15
- package/build/elements/context-menu.js +6 -9
- package/build/elements/dialog.d.ts +10 -10
- package/build/elements/dialog.js +2 -6
- package/build/elements/drawer.d.ts +14 -12
- package/build/elements/drawer.js +14 -15
- package/build/elements/dropdown-menu.d.ts +15 -15
- package/build/elements/dropdown-menu.js +7 -10
- package/build/elements/empty.d.ts +6 -6
- package/build/elements/empty.js +2 -2
- package/build/elements/field.d.ts +10 -10
- package/build/elements/field.js +3 -4
- package/build/elements/hover-card.d.ts +3 -3
- package/build/elements/hover-card.js +2 -2
- package/build/elements/input-group.d.ts +7 -7
- package/build/elements/input-group.js +5 -5
- package/build/elements/input-otp.d.ts +4 -4
- package/build/elements/input.d.ts +1 -1
- package/build/elements/input.js +2 -2
- package/build/elements/item.d.ts +9 -9
- package/build/elements/item.js +2 -2
- package/build/elements/kbd.d.ts +2 -2
- package/build/elements/label.d.ts +1 -1
- package/build/elements/menubar.d.ts +16 -16
- package/build/elements/menubar.js +9 -11
- package/build/elements/native-select.d.ts +3 -3
- package/build/elements/native-select.js +2 -4
- package/build/elements/navigation-menu.d.ts +8 -8
- package/build/elements/navigation-menu.js +3 -5
- package/build/elements/pagination.d.ts +7 -7
- package/build/elements/pagination.js +4 -10
- package/build/elements/popover.d.ts +6 -6
- package/build/elements/popover.js +2 -2
- package/build/elements/progress.d.ts +5 -5
- package/build/elements/progress.js +2 -3
- package/build/elements/radio-group.d.ts +2 -2
- package/build/elements/resizable.d.ts +3 -3
- package/build/elements/scroll-area.d.ts +2 -2
- package/build/elements/scroll-area.js +2 -4
- package/build/elements/select.d.ts +9 -9
- package/build/elements/select.js +4 -9
- package/build/elements/separator.d.ts +1 -1
- package/build/elements/sheet.d.ts +8 -8
- package/build/elements/sheet.js +2 -6
- package/build/elements/sidebar.d.ts +19 -19
- package/build/elements/sidebar.js +15 -37
- package/build/elements/skeleton.d.ts +1 -1
- package/build/elements/slider.d.ts +2 -1
- package/build/elements/slider.js +2 -3
- package/build/elements/sonner.d.ts +1 -1
- package/build/elements/spinner.d.ts +1 -1
- package/build/elements/switch.d.ts +1 -1
- package/build/elements/table.d.ts +8 -8
- package/build/elements/tabs.d.ts +4 -4
- package/build/elements/tabs.js +3 -3
- package/build/elements/textarea.d.ts +1 -1
- package/build/elements/textarea.js +2 -2
- package/build/elements/toggle-group.d.ts +3 -2
- package/build/elements/toggle-group.js +3 -3
- package/build/elements/toggle.d.ts +1 -1
- package/build/elements/toggle.js +2 -2
- package/build/elements/tooltip.d.ts +4 -4
- package/build/elements/tooltip.js +2 -3
- package/build/head.d.ts +4 -0
- package/build/head.js +6 -0
- package/build/helpers/agent-skills.d.ts +22 -0
- package/build/helpers/agent-skills.js +46 -0
- package/build/helpers/agent-skills.unit.d.ts +1 -0
- package/build/helpers/agent-skills.unit.js +49 -0
- package/build/helpers/article-search.d.ts +19 -0
- package/build/helpers/article-search.js +134 -0
- package/build/helpers/article.d.ts +13 -0
- package/build/helpers/article.js +17 -1
- package/build/helpers/dependency-dir.d.ts +5 -0
- package/build/helpers/dependency-dir.js +20 -0
- package/build/helpers/dependency-dir.unit.d.ts +1 -0
- package/build/helpers/dependency-dir.unit.js +25 -0
- package/build/helpers/dynamic-icon-resolver.d.ts +1 -1
- package/build/helpers/dynamic-icon.d.ts +1 -1
- package/build/helpers/head.d.ts +58 -0
- package/build/helpers/head.js +80 -0
- package/build/helpers/head.unit.d.ts +1 -0
- package/build/helpers/head.unit.js +110 -0
- package/build/helpers/llms.d.ts +33 -0
- package/build/helpers/llms.js +72 -0
- package/build/helpers/llms.unit.d.ts +1 -0
- package/build/helpers/llms.unit.js +98 -0
- package/build/helpers/nav-items.d.ts +11 -0
- package/build/helpers/nav-items.js +65 -0
- package/build/helpers/render-inline-markdown.d.ts +1 -0
- package/build/helpers/render-inline-markdown.js +34 -0
- package/build/helpers/render-inline-markdown.unit.d.ts +1 -0
- package/build/helpers/render-inline-markdown.unit.js +20 -0
- package/build/helpers/search-highlight.d.ts +8 -0
- package/build/helpers/search-highlight.js +59 -0
- package/build/helpers/search-highlight.unit.d.ts +1 -0
- package/build/helpers/search-highlight.unit.js +36 -0
- package/build/helpers/target-export.d.ts +9 -0
- package/build/helpers/target-export.js +24 -0
- package/build/helpers/target-export.unit.d.ts +1 -0
- package/build/helpers/target-export.unit.js +25 -0
- package/build/helpers/theme.d.ts +6 -2
- package/build/helpers/theme.js +40 -7
- package/build/helpers/theme.unit.d.ts +1 -0
- package/build/helpers/theme.unit.js +30 -0
- package/build/hooks/hydrated.d.ts +2 -0
- package/build/hooks/hydrated.js +15 -0
- package/build/index.d.ts +1 -0
- package/build/index.js +1 -1
- package/build/models/config.d.ts +52 -6
- package/build/models/config.js +17 -1
- package/build/models/section.d.ts +4 -2
- package/build/models/section.js +1 -1
- package/build/plugins/vite-agents.d.ts +17 -0
- package/build/plugins/vite-agents.js +109 -0
- package/build/plugins/vite-agents.unit.d.ts +1 -0
- package/build/plugins/vite-agents.unit.js +20 -0
- package/build/plugins/vite-livemark.d.ts +4 -5
- package/build/plugins/vite-livemark.js +56 -90
- package/build/plugins/vite-robots.d.ts +4 -2
- package/build/plugins/vite-robots.js +15 -2
- package/build/plugins/vite-robots.unit.js +5 -1
- package/build/program.js +4 -2
- package/build/routes/$.d.ts +4 -1
- package/build/routes/$.js +20 -9
- package/build/routes/__root.js +29 -63
- package/build/settings.js +2 -1
- package/build/vite.config.js +3 -1
- package/package.json +69 -66
- package/{.agents/skills → skills}/livemark-authoring/SKILL.md +2 -2
- package/{.agents/skills → skills}/livemark-configuration/SKILL.md +16 -5
- package/skills/livemark-customization/SKILL.md +146 -0
- package/source/actions/config/define.unit.ts +9 -0
- package/source/actions/escape/copy.ts +35 -0
- package/source/actions/escape/copy.unit.ts +51 -0
- package/source/actions/escape/list.ts +30 -0
- package/source/actions/escape/list.unit.ts +42 -0
- package/source/actions/target/build.ts +49 -69
- package/source/actions/target/build.unit.ts +95 -0
- package/source/actions/target/clear.ts +14 -0
- package/source/actions/target/clear.unit.ts +25 -0
- package/source/commands/build.ts +0 -1
- package/source/commands/clear.ts +15 -0
- package/source/commands/escape.ts +42 -59
- package/source/commands/preview.ts +0 -1
- package/source/commands/start.ts +0 -1
- package/source/components/AnsiCode.tsx +1 -1
- package/source/components/Article.tsx +8 -4
- package/source/components/Banner.tsx +12 -2
- package/source/components/BlogIndex.tsx +1 -1
- package/source/components/Callout.tsx +4 -6
- package/source/components/ChangelogIndex.tsx +1 -1
- package/source/components/ChoiceList.tsx +69 -0
- package/source/components/CodeBlock.tsx +6 -6
- package/source/components/CodeTabs.tsx +1 -1
- package/source/components/ContentTabs.tsx +1 -1
- package/source/components/DefaultCatchBoundary.tsx +4 -6
- package/source/components/Details.tsx +1 -1
- package/source/components/FileTree.tsx +1 -1
- package/source/components/Header.tsx +52 -0
- package/source/components/HeaderMenu.tsx +41 -0
- package/source/components/HeaderNav.tsx +145 -0
- package/source/components/HeaderSearch.tsx +77 -0
- package/source/components/InlineBadge.tsx +1 -1
- package/source/components/InlineToc.tsx +1 -1
- package/source/components/Layout.tsx +16 -160
- package/source/components/LinkButton.tsx +9 -1
- package/source/components/MenuSheet.tsx +92 -0
- package/source/components/MermaidRenderer.tsx +19 -8
- package/source/components/NotFound.tsx +3 -5
- package/source/components/PackageTabs.tsx +2 -2
- package/source/components/PageToolbar.tsx +11 -3
- package/source/components/PrevNext.tsx +2 -2
- package/source/components/Root.tsx +13 -0
- package/source/components/Search.tsx +28 -8
- package/source/components/SearchDialog.tsx +15 -189
- package/source/components/SheetPanel.tsx +69 -0
- package/source/components/Sidebar.tsx +27 -14
- package/source/components/SidebarArticles.tsx +4 -3
- package/source/components/SidebarLinks.tsx +3 -2
- package/source/components/SidebarPosts.tsx +3 -2
- package/source/components/SidebarReleases.tsx +3 -2
- package/source/components/SidebarSectionPicker.tsx +70 -0
- package/source/components/SiteTitle.tsx +5 -5
- package/source/components/SoundCloud.tsx +1 -1
- package/source/components/TagIndex.tsx +1 -1
- package/source/components/Theme.tsx +55 -29
- package/source/components/Toc.tsx +76 -55
- package/source/components/YouTube.tsx +1 -1
- package/source/content/article.ts +8 -13
- package/source/content-collections.ts +5 -4
- package/source/elements/alert-dialog.tsx +1 -1
- package/source/elements/alert.tsx +1 -1
- package/source/elements/badge.tsx +4 -0
- package/source/elements/button-group.tsx +4 -4
- package/source/elements/button.tsx +10 -6
- package/source/elements/calendar.tsx +1 -1
- package/source/elements/card.tsx +3 -3
- package/source/elements/command.tsx +7 -4
- package/source/elements/context-menu.tsx +5 -5
- package/source/elements/dialog.tsx +1 -1
- package/source/elements/drawer.tsx +59 -43
- package/source/elements/dropdown-menu.tsx +6 -6
- package/source/elements/empty.tsx +1 -1
- package/source/elements/field.tsx +1 -1
- package/source/elements/hover-card.tsx +1 -1
- package/source/elements/input-group.tsx +4 -4
- package/source/elements/input.tsx +1 -1
- package/source/elements/item.tsx +1 -1
- package/source/elements/menubar.tsx +8 -8
- package/source/elements/native-select.tsx +1 -1
- package/source/elements/popover.tsx +1 -1
- package/source/elements/select.tsx +3 -3
- package/source/elements/sidebar.tsx +8 -21
- package/source/elements/tabs.tsx +2 -2
- package/source/elements/textarea.tsx +1 -1
- package/source/elements/toggle-group.tsx +2 -2
- package/source/elements/toggle.tsx +1 -1
- package/source/env.d.ts +1 -0
- package/source/head.ts +7 -0
- package/source/helpers/agent-skills.ts +59 -0
- package/source/helpers/agent-skills.unit.ts +56 -0
- package/source/helpers/article-search.tsx +192 -0
- package/source/helpers/article.ts +17 -0
- package/source/helpers/dependency-dir.ts +20 -0
- package/source/helpers/dependency-dir.unit.ts +39 -0
- package/source/helpers/head.ts +105 -0
- package/source/helpers/head.unit.ts +118 -0
- package/source/helpers/llms.ts +109 -0
- package/source/helpers/llms.unit.ts +115 -0
- package/source/helpers/nav-items.tsx +102 -0
- package/source/helpers/render-inline-markdown.ts +44 -0
- package/source/helpers/render-inline-markdown.unit.ts +30 -0
- package/source/helpers/search-highlight.ts +73 -0
- package/source/helpers/search-highlight.unit.ts +42 -0
- package/source/helpers/target-export.ts +28 -0
- package/source/helpers/target-export.unit.ts +37 -0
- package/source/helpers/theme.ts +40 -6
- package/source/helpers/theme.unit.ts +32 -0
- package/source/hooks/hydrated.ts +18 -0
- package/source/index.ts +1 -0
- package/source/models/config.ts +24 -0
- package/source/models/section.ts +3 -0
- package/source/plugins/vite-agents.ts +135 -0
- package/source/plugins/vite-agents.unit.ts +26 -0
- package/source/plugins/vite-livemark.ts +54 -95
- package/source/plugins/vite-robots.ts +18 -3
- package/source/plugins/vite-robots.unit.ts +7 -0
- package/source/program.ts +2 -0
- package/source/routes/$.tsx +27 -9
- package/source/routes/__root.tsx +51 -71
- package/source/settings.ts +1 -0
- package/source/styles/general.css +59 -11
- package/source/styles/markdown.css +13 -7
- package/source/styles/shape.css +14 -0
- package/source/vite.config.ts +2 -0
- package/.agents/skills/change-review/SKILL.md +0 -47
- package/.agents/skills/livemark-customization/SKILL.md +0 -118
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { useSyncExternalStore } from "react"
|
|
2
|
+
|
|
3
|
+
/** False during SSR and the hydrating render, true from the first client render on. */
|
|
4
|
+
export function useHydrated() {
|
|
5
|
+
return useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot)
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
function subscribe() {
|
|
9
|
+
return () => {}
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function getSnapshot() {
|
|
13
|
+
return true
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function getServerSnapshot() {
|
|
17
|
+
return false
|
|
18
|
+
}
|
package/source/index.ts
CHANGED
package/source/models/config.ts
CHANGED
|
@@ -22,6 +22,24 @@ export type ViteOverride =
|
|
|
22
22
|
* resolves it to a string before any runtime sees it. */
|
|
23
23
|
export type Base = string | ((command: "build" | "serve") => string | undefined)
|
|
24
24
|
|
|
25
|
+
/** Extra site-wide `<head>` tags, appended after Livemark's defaults. A meta tag
|
|
26
|
+
* sharing a default's `name` or `property` replaces it. */
|
|
27
|
+
export type Head = z.infer<typeof Head>
|
|
28
|
+
export const Head = z.object({
|
|
29
|
+
meta: z.array(z.record(z.string(), z.string())).optional(),
|
|
30
|
+
links: z.array(z.record(z.string(), z.string())).optional(),
|
|
31
|
+
scripts: z.array(z.record(z.string(), z.string())).optional(),
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
/** How automated systems may use the site's content, published as a
|
|
35
|
+
* `Content-Signal` line in the generated `robots.txt`. */
|
|
36
|
+
export type ContentSignals = z.infer<typeof ContentSignals>
|
|
37
|
+
export const ContentSignals = z.object({
|
|
38
|
+
search: z.boolean().default(true),
|
|
39
|
+
aiInput: z.boolean().default(true),
|
|
40
|
+
aiTrain: z.boolean().default(true),
|
|
41
|
+
})
|
|
42
|
+
|
|
25
43
|
/**
|
|
26
44
|
* User-provided configuration for Livemark.
|
|
27
45
|
*
|
|
@@ -46,6 +64,7 @@ export const UserConfig = z.object({
|
|
|
46
64
|
logo: z.string().optional(),
|
|
47
65
|
title: z.string().default("Livemark"),
|
|
48
66
|
description: z.string().default("Markdown site generator"),
|
|
67
|
+
banner: z.string().optional(),
|
|
49
68
|
include: z.union([z.string(), z.array(z.string())]).default("**/*.md"),
|
|
50
69
|
exclude: z.union([z.string(), z.array(z.string())]).optional(),
|
|
51
70
|
codeThemeLight: CodeThemeLight.default("catppuccin-latte"),
|
|
@@ -53,6 +72,9 @@ export const UserConfig = z.object({
|
|
|
53
72
|
links: z.array(Link).optional(),
|
|
54
73
|
sections: z.array(Section).optional(),
|
|
55
74
|
patches: z.array(Patch).optional(),
|
|
75
|
+
head: Head.optional(),
|
|
76
|
+
contentSignals: ContentSignals.prefault({}),
|
|
77
|
+
skills: z.union([z.string(), z.array(z.string())]).optional(),
|
|
56
78
|
// Stored as `unknown` in the schema (zod can't type a Vite UserConfig
|
|
57
79
|
// or a function returning one meaningfully); the public TypeScript
|
|
58
80
|
// surface above lifts it back to ViteOverride for editor support.
|
|
@@ -69,8 +91,10 @@ export const WebsiteConfig = UserConfig.pick({
|
|
|
69
91
|
site: true,
|
|
70
92
|
favicon: true,
|
|
71
93
|
logo: true,
|
|
94
|
+
banner: true,
|
|
72
95
|
links: true,
|
|
73
96
|
sections: true,
|
|
97
|
+
head: true,
|
|
74
98
|
}).extend({
|
|
75
99
|
base: z.string().optional(),
|
|
76
100
|
})
|
package/source/models/section.ts
CHANGED
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from "node:fs"
|
|
2
|
+
import { join, resolve } from "node:path"
|
|
3
|
+
import { pathToFileURL } from "node:url"
|
|
4
|
+
import type { Plugin } from "vite"
|
|
5
|
+
import { z } from "zod"
|
|
6
|
+
import {
|
|
7
|
+
buildSkillFileName,
|
|
8
|
+
loadAgentSkills,
|
|
9
|
+
renderSkillsIndex,
|
|
10
|
+
} from "../helpers/agent-skills.ts"
|
|
11
|
+
import {
|
|
12
|
+
buildTwinFileName,
|
|
13
|
+
renderLlmsText,
|
|
14
|
+
renderMarkdownTwin,
|
|
15
|
+
renderSectionIndex,
|
|
16
|
+
} from "../helpers/llms.ts"
|
|
17
|
+
import { resolveIncludes } from "../helpers/resolve-includes.ts"
|
|
18
|
+
import type { Config } from "../models/config.ts"
|
|
19
|
+
|
|
20
|
+
export interface AgentsOptions {
|
|
21
|
+
config: Config
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const GeneratedArticles = z.array(
|
|
25
|
+
z.object({
|
|
26
|
+
path: z.string(),
|
|
27
|
+
title: z.string(),
|
|
28
|
+
description: z.string().optional(),
|
|
29
|
+
order: z.number().optional(),
|
|
30
|
+
date: z.string().optional(),
|
|
31
|
+
file: z.string(),
|
|
32
|
+
content: z.string(),
|
|
33
|
+
}),
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
/** Emit what AI agents look for into the client build: a Markdown twin of
|
|
37
|
+
* every article and section listing at `<path>index.md`, `llms.txt`, the Agent Skills index for
|
|
38
|
+
* `config.skills`, and a `_headers` file whose `Link` points agents at them. */
|
|
39
|
+
export function agents(opts: AgentsOptions): Plugin {
|
|
40
|
+
const publicDir = join(opts.config.root, ".livemark", "public")
|
|
41
|
+
let root = ""
|
|
42
|
+
return {
|
|
43
|
+
name: "livemark:agents",
|
|
44
|
+
applyToEnvironment(env) {
|
|
45
|
+
return env.name === "client"
|
|
46
|
+
},
|
|
47
|
+
configResolved(resolved) {
|
|
48
|
+
root = resolved.root
|
|
49
|
+
},
|
|
50
|
+
async generateBundle() {
|
|
51
|
+
// Vite copies the publicDir before writeBundle, so emitting a file the
|
|
52
|
+
// project ships under `.livemark/public/` would silently replace it.
|
|
53
|
+
const emit = (fileName: string, source: string | Buffer) => {
|
|
54
|
+
if (existsSync(join(publicDir, fileName))) return
|
|
55
|
+
this.emitFile({ type: "asset", fileName, source })
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const articles = await loadGeneratedArticles(root)
|
|
59
|
+
for (const article of articles) {
|
|
60
|
+
const content = resolveIncludes(
|
|
61
|
+
article.content,
|
|
62
|
+
resolve(opts.config.root, article.file),
|
|
63
|
+
)
|
|
64
|
+
emit(buildTwinFileName(article.path), renderMarkdownTwin({ article, content }))
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const llms = renderLlmsText({ config: opts.config, articles })
|
|
68
|
+
emit("llms.txt", llms)
|
|
69
|
+
const hasTwin = (path: string) => articles.some(a => a.path === path)
|
|
70
|
+
if (!hasTwin("/")) emit("index.md", llms)
|
|
71
|
+
// A rewrite to a missing twin is a 404, so every listing page needs one too.
|
|
72
|
+
for (const section of opts.config.sections ?? []) {
|
|
73
|
+
if (section.type === "custom" || section.prefix === "/") continue
|
|
74
|
+
if (hasTwin(section.prefix)) continue
|
|
75
|
+
emit(
|
|
76
|
+
buildTwinFileName(section.prefix),
|
|
77
|
+
renderSectionIndex({ config: opts.config, section, articles }),
|
|
78
|
+
)
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const patterns = opts.config.skills ?? []
|
|
82
|
+
const skills = loadAgentSkills({
|
|
83
|
+
root: opts.config.root,
|
|
84
|
+
patterns: Array.isArray(patterns) ? patterns : [patterns],
|
|
85
|
+
})
|
|
86
|
+
for (const skill of skills) emit(buildSkillFileName(skill.name), skill.source)
|
|
87
|
+
if (skills.length) {
|
|
88
|
+
emit(
|
|
89
|
+
".well-known/agent-skills/index.json",
|
|
90
|
+
renderSkillsIndex({ skills, base: opts.config.base }),
|
|
91
|
+
)
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const headersFile = join(publicDir, "_headers")
|
|
95
|
+
this.emitFile({
|
|
96
|
+
type: "asset",
|
|
97
|
+
fileName: "_headers",
|
|
98
|
+
source: renderHeaders({
|
|
99
|
+
base: opts.config.base,
|
|
100
|
+
hasSkills: skills.length > 0,
|
|
101
|
+
existing: existsSync(headersFile) ? readFileSync(headersFile, "utf-8") : "",
|
|
102
|
+
}),
|
|
103
|
+
})
|
|
104
|
+
},
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/** Render `_headers` (Cloudflare, Netlify): a `Link` on the home page naming
|
|
109
|
+
* `llms.txt`, its Markdown twin and the skills index, after any rules the
|
|
110
|
+
* project ships itself. */
|
|
111
|
+
export function renderHeaders(props: {
|
|
112
|
+
base?: string
|
|
113
|
+
hasSkills: boolean
|
|
114
|
+
existing?: string
|
|
115
|
+
}) {
|
|
116
|
+
const base = props.base ?? ""
|
|
117
|
+
const links = [
|
|
118
|
+
`<${base}/llms.txt>; rel="describedby"; type="text/plain"`,
|
|
119
|
+
`<${base}/index.md>; rel="alternate"; type="text/markdown"`,
|
|
120
|
+
]
|
|
121
|
+
if (props.hasSkills) {
|
|
122
|
+
links.push(
|
|
123
|
+
`<${base}/.well-known/agent-skills/index.json>; rel="describedby"; type="application/json"`,
|
|
124
|
+
)
|
|
125
|
+
}
|
|
126
|
+
const rule = `${base}/\n Link: ${links.join(", ")}\n`
|
|
127
|
+
const existing = props.existing?.trimEnd()
|
|
128
|
+
return existing ? `${existing}\n\n${rule}` : rule
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
async function loadGeneratedArticles(root: string) {
|
|
132
|
+
const file = join(root, ".content-collections", "generated", "allArticles.js")
|
|
133
|
+
const module: { default: unknown } = await import(pathToFileURL(file).href)
|
|
134
|
+
return GeneratedArticles.parse(module.default)
|
|
135
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { describe, expect, it } from "vite-plus/test"
|
|
2
|
+
import { renderHeaders } from "./vite-agents.ts"
|
|
3
|
+
|
|
4
|
+
describe("renderHeaders", () => {
|
|
5
|
+
it("links the home page to llms.txt and its markdown twin", () => {
|
|
6
|
+
expect(renderHeaders({ hasSkills: false })).toBe(
|
|
7
|
+
'/\n Link: </llms.txt>; rel="describedby"; type="text/plain", </index.md>; rel="alternate"; type="text/markdown"\n',
|
|
8
|
+
)
|
|
9
|
+
})
|
|
10
|
+
|
|
11
|
+
it("adds the skills index and mounts everything under base", () => {
|
|
12
|
+
const headers = renderHeaders({ base: "/repo", hasSkills: true })
|
|
13
|
+
expect(headers.startsWith("/repo/\n Link: </repo/llms.txt>")).toBe(true)
|
|
14
|
+
expect(headers).toContain(
|
|
15
|
+
'</repo/.well-known/agent-skills/index.json>; rel="describedby"; type="application/json"',
|
|
16
|
+
)
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
it("keeps the project's own rules ahead of the generated one", () => {
|
|
20
|
+
const headers = renderHeaders({
|
|
21
|
+
hasSkills: false,
|
|
22
|
+
existing: "/*\n X-Frame-Options: DENY\n\n",
|
|
23
|
+
})
|
|
24
|
+
expect(headers.startsWith("/*\n X-Frame-Options: DENY\n\n/\n Link: ")).toBe(true)
|
|
25
|
+
})
|
|
26
|
+
})
|
|
@@ -1,9 +1,11 @@
|
|
|
1
|
-
import { cpSync, existsSync, rmSync } from "node:fs"
|
|
1
|
+
import { cpSync, existsSync, rmSync, statSync } from "node:fs"
|
|
2
2
|
import { join, sep } from "node:path"
|
|
3
|
-
import {
|
|
4
|
-
import type { ConfigEnv, Plugin, UserConfig as ViteUserConfig, ViteDevServer } from "vite"
|
|
3
|
+
import type { ConfigEnv, Plugin, UserConfig as ViteUserConfig } from "vite"
|
|
5
4
|
import { mergeConfig } from "vite"
|
|
5
|
+
import packageJson from "../../package.json" with { type: "json" }
|
|
6
6
|
import { buildTarget } from "../actions/target/build.ts"
|
|
7
|
+
import { renderInlineMarkdown } from "../helpers/render-inline-markdown.ts"
|
|
8
|
+
import { resolveTargetExport } from "../helpers/target-export.ts"
|
|
7
9
|
import type { Config } from "../models/config.ts"
|
|
8
10
|
import { WebsiteConfig } from "../models/config.ts"
|
|
9
11
|
import { ESCAPABLE_FILES, ESCAPABLE_FOLDERS } from "../settings.ts"
|
|
@@ -13,13 +15,15 @@ export interface LivemarkOptions {
|
|
|
13
15
|
}
|
|
14
16
|
|
|
15
17
|
const SOURCE_DIR = join(import.meta.dirname, "..")
|
|
18
|
+
const IS_INSTALLED = SOURCE_DIR.includes(`${sep}node_modules${sep}`)
|
|
16
19
|
|
|
17
20
|
/** Single virtual module exposing livemark's runtime values to consumer
|
|
18
21
|
* override files via named exports:
|
|
19
22
|
*
|
|
20
23
|
* import { config } from "livemark:virtual"
|
|
21
24
|
*
|
|
22
|
-
* Currently exports `config` (validated `WebsiteConfig`)
|
|
25
|
+
* Currently exports `config` (validated `WebsiteConfig`) and `bannerHtml`
|
|
26
|
+
* (`config.banner` rendered from inline Markdown at build time); future
|
|
23
27
|
* additions (e.g. `articles`, `sections`) land here as new named
|
|
24
28
|
* exports without consumers changing their import statements.
|
|
25
29
|
*
|
|
@@ -29,33 +33,36 @@ const SOURCE_DIR = join(import.meta.dirname, "..")
|
|
|
29
33
|
const VIRTUAL_ID = "livemark:virtual"
|
|
30
34
|
const VIRTUAL_RESOLVED_ID = `\0${VIRTUAL_ID}`
|
|
31
35
|
|
|
32
|
-
/** Owns the
|
|
36
|
+
/** Owns the project's `.livemark/.target/` mount: synthesizes it via
|
|
33
37
|
* `buildTarget()`, returns the Vite config that anchors there, and
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
* redirection, no realpath surprises. */
|
|
38
|
+
* copies `.livemark/` edits (plus livemark's `source/` in its own dev)
|
|
39
|
+
* into the target. Vite watches the target natively, so its own HMR
|
|
40
|
+
* decides between Fast Refresh, CSS updates and a page reload. */
|
|
38
41
|
export function livemark(opts: LivemarkOptions): Plugin {
|
|
39
42
|
const overridesRoot = join(opts.config.root, ".livemark")
|
|
43
|
+
const outDir = join(overridesRoot, "build")
|
|
40
44
|
let targetDir = ""
|
|
41
45
|
return {
|
|
42
46
|
name: "livemark",
|
|
43
47
|
enforce: "pre",
|
|
44
48
|
async config(_, env) {
|
|
45
|
-
targetDir = buildTarget(
|
|
49
|
+
targetDir = buildTarget({
|
|
50
|
+
configPath: opts.config.configPath,
|
|
51
|
+
sourceDir: SOURCE_DIR,
|
|
52
|
+
})
|
|
46
53
|
const base: ViteUserConfig = {
|
|
47
54
|
root: targetDir,
|
|
55
|
+
// The default `<root>/node_modules/.vite` would land inside the
|
|
56
|
+
// dependency link, i.e. in livemark's shared install.
|
|
57
|
+
cacheDir: join(targetDir, ".vite"),
|
|
48
58
|
// Mount everything (asset URLs, modulepreloads, dev server) under
|
|
49
59
|
// `config.base` so the same build works at root or at a sub-path
|
|
50
60
|
// (e.g. GitHub Pages `https://user.github.io/repo/`). The router
|
|
51
61
|
// and asset-path resolver mirror this at runtime.
|
|
52
62
|
base: opts.config.base ? `${opts.config.base}/` : "/",
|
|
53
|
-
// outDir lives outside the target root
|
|
54
|
-
//
|
|
55
|
-
|
|
56
|
-
// empty out-of-root directories doesn't apply here because the
|
|
57
|
-
// target/ root is livemark's, not the consumer's.
|
|
58
|
-
build: { outDir: join(overridesRoot, "build"), emptyOutDir: true },
|
|
63
|
+
// outDir lives outside the target root, so Vite's refusal to
|
|
64
|
+
// empty out-of-root directories needs `emptyOutDir`.
|
|
65
|
+
build: { outDir, emptyOutDir: true },
|
|
59
66
|
resolve: { dedupe: ["react", "react-dom"] },
|
|
60
67
|
// For the SSR build only, inline every node_modules dep into
|
|
61
68
|
// server.js. The prerender step (which runs server.js with
|
|
@@ -72,62 +79,44 @@ export function livemark(opts: LivemarkOptions): Plugin {
|
|
|
72
79
|
},
|
|
73
80
|
resolveId(id) {
|
|
74
81
|
if (id === VIRTUAL_ID) return VIRTUAL_RESOLVED_ID
|
|
75
|
-
return null
|
|
82
|
+
return resolveTargetExport({ id, targetDir, exports: packageJson.exports }) ?? null
|
|
76
83
|
},
|
|
77
84
|
load(id) {
|
|
78
85
|
if (id !== VIRTUAL_RESOLVED_ID) return null
|
|
79
86
|
const config = WebsiteConfig.parse(opts.config)
|
|
80
|
-
|
|
87
|
+
const bannerHtml = config.banner
|
|
88
|
+
? renderInlineMarkdown(config.banner, config.base)
|
|
89
|
+
: undefined
|
|
90
|
+
return [
|
|
91
|
+
`export const config = ${JSON.stringify(config)}`,
|
|
92
|
+
`export const bannerHtml = ${JSON.stringify(bannerHtml)}`,
|
|
93
|
+
].join("\n")
|
|
81
94
|
},
|
|
82
95
|
configureServer(server) {
|
|
83
|
-
//
|
|
84
|
-
//
|
|
85
|
-
//
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
// `server.watcher` after cpSync — see {@link notifyVite}.
|
|
93
|
-
// A dedicated chokidar 5 watcher set up directly on these paths
|
|
94
|
-
// fires reliably; we forward events into Vite's HMR pipeline below.
|
|
95
|
-
const overlayPaths: string[] = []
|
|
96
|
-
for (const folder of ESCAPABLE_FOLDERS) {
|
|
97
|
-
overlayPaths.push(join(overridesRoot, folder))
|
|
98
|
-
}
|
|
99
|
-
for (const file of ESCAPABLE_FILES) {
|
|
100
|
-
overlayPaths.push(join(overridesRoot, file))
|
|
101
|
-
}
|
|
102
|
-
const watcher = chokidarWatch(
|
|
103
|
-
[opts.config.configPath, ...overlayPaths, SOURCE_DIR],
|
|
104
|
-
{ ignoreInitial: true, ignorePermissionErrors: true },
|
|
105
|
-
)
|
|
106
|
-
watcher.on("change", file => onChange(file, "change"))
|
|
107
|
-
watcher.on("add", file => onChange(file, "add"))
|
|
108
|
-
watcher.on("unlink", file => onChange(file, "unlink"))
|
|
109
|
-
const closeWatcher = () => {
|
|
110
|
-
watcher.close()
|
|
111
|
-
}
|
|
112
|
-
server.httpServer?.once("close", closeWatcher)
|
|
96
|
+
// Vite watches only its root (the target); the files the target is
|
|
97
|
+
// copied from join its watcher here. `.livemark/` always exists
|
|
98
|
+
// because the target lives inside it.
|
|
99
|
+
server.watcher.add([opts.config.configPath, overridesRoot])
|
|
100
|
+
if (!IS_INSTALLED) server.watcher.add(SOURCE_DIR)
|
|
101
|
+
server.watcher.on("change", file => onChange(file, "change"))
|
|
102
|
+
server.watcher.on("add", file => onChange(file, "add"))
|
|
103
|
+
server.watcher.on("unlink", file => onChange(file, "unlink"))
|
|
104
|
+
server.watcher.on("unlinkDir", file => onChange(file, "unlink"))
|
|
113
105
|
|
|
114
106
|
function onChange(file: string, kind: "change" | "add" | "unlink") {
|
|
115
107
|
if (file === opts.config.configPath) {
|
|
116
108
|
server.restart()
|
|
117
109
|
return
|
|
118
110
|
}
|
|
119
|
-
|
|
111
|
+
if (isWithin(file, targetDir) || isWithin(file, outDir)) return
|
|
112
|
+
const overlayHit = matchPath(file, overridesRoot)
|
|
120
113
|
if (overlayHit) {
|
|
121
114
|
syncOverlay(overlayHit, kind, targetDir, overridesRoot)
|
|
122
|
-
notifyVite(server, join(targetDir, overlayHit.rel), kind)
|
|
123
|
-
if (kind !== "change") fullReload(server)
|
|
124
115
|
return
|
|
125
116
|
}
|
|
126
|
-
const sourceHit =
|
|
127
|
-
if (sourceHit) {
|
|
117
|
+
const sourceHit = matchPath(file, SOURCE_DIR)
|
|
118
|
+
if (sourceHit && !sourceHit.rel.includes("routeTree.gen.ts")) {
|
|
128
119
|
syncSource(sourceHit, kind, targetDir, overridesRoot)
|
|
129
|
-
notifyVite(server, join(targetDir, sourceHit.rel), kind)
|
|
130
|
-
if (kind !== "change") fullReload(server)
|
|
131
120
|
}
|
|
132
121
|
}
|
|
133
122
|
},
|
|
@@ -139,21 +128,14 @@ interface Hit {
|
|
|
139
128
|
category: "folder" | "file" | null
|
|
140
129
|
}
|
|
141
130
|
|
|
142
|
-
function
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
const rel = file.slice(prefix.length)
|
|
131
|
+
function matchPath(file: string, root: string): Hit | null {
|
|
132
|
+
if (!isWithin(file, root) || file === root) return null
|
|
133
|
+
const rel = file.slice(root.length + sep.length)
|
|
146
134
|
return { rel, category: classify(rel) }
|
|
147
135
|
}
|
|
148
136
|
|
|
149
|
-
function
|
|
150
|
-
|
|
151
|
-
if (!file.startsWith(prefix)) return null
|
|
152
|
-
// Skip the targets/ output dir itself (sibling of source/) and any
|
|
153
|
-
// `.gen.ts` files that downstream tools regenerate inside the target.
|
|
154
|
-
const rel = file.slice(prefix.length)
|
|
155
|
-
if (rel === "" || rel.includes("routeTree.gen.ts")) return null
|
|
156
|
-
return { rel, category: classify(rel) }
|
|
137
|
+
function isWithin(file: string, dir: string) {
|
|
138
|
+
return file === dir || file.startsWith(`${dir}${sep}`)
|
|
157
139
|
}
|
|
158
140
|
|
|
159
141
|
function classify(rel: string): "folder" | "file" | null {
|
|
@@ -175,7 +157,11 @@ function syncOverlay(
|
|
|
175
157
|
const dest = join(targetDir, hit.rel)
|
|
176
158
|
const source = join(SOURCE_DIR, hit.rel)
|
|
177
159
|
if (kind === "unlink") {
|
|
178
|
-
|
|
160
|
+
// Copy a file's default over the escape in one write: an unlink then
|
|
161
|
+
// an add reaches Vite as two events and costs a page reload.
|
|
162
|
+
if (!existsSync(source) || statSync(source).isDirectory()) {
|
|
163
|
+
rmSync(dest, { force: true, recursive: true })
|
|
164
|
+
}
|
|
179
165
|
if (existsSync(source)) cpSync(source, dest, { recursive: true, force: true })
|
|
180
166
|
return
|
|
181
167
|
}
|
|
@@ -201,33 +187,6 @@ function syncSource(
|
|
|
201
187
|
if (existsSync(source)) cpSync(source, dest, { recursive: true, force: true })
|
|
202
188
|
}
|
|
203
189
|
|
|
204
|
-
function fullReload(server: ViteDevServer) {
|
|
205
|
-
server.ws.send({ type: "full-reload" })
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
/** Drive Vite's HMR pipeline manually for a target file we just synced.
|
|
209
|
-
* Vite's chokidar ignores `**\/node_modules/**` by default and our
|
|
210
|
-
* targetDir lives inside livemark's own node_modules in consumer
|
|
211
|
-
* projects, so cpSync writes there are silently filtered out.
|
|
212
|
-
*
|
|
213
|
-
* Emitting on `server.watcher` runs Vite's HMR analysis per environment
|
|
214
|
-
* (CSS gets `hmr update`, route modules trigger `(ssr) page reload`).
|
|
215
|
-
* But `(ssr) page reload` only sends `full-reload` on the SSR
|
|
216
|
-
* environment's hot channel — the browser is connected to the *client*
|
|
217
|
-
* channel and never sees it. So we also fire `full-reload` on the
|
|
218
|
-
* client hot directly, which is what TanStack Start needs for route
|
|
219
|
-
* edits to actually refresh the page. CSS HMR still works because
|
|
220
|
-
* Vite's analysis runs first and updates already-flushed before our
|
|
221
|
-
* reload. */
|
|
222
|
-
function notifyVite(
|
|
223
|
-
server: ViteDevServer,
|
|
224
|
-
targetFile: string,
|
|
225
|
-
kind: "change" | "add" | "unlink",
|
|
226
|
-
) {
|
|
227
|
-
server.watcher.emit(kind, targetFile)
|
|
228
|
-
server.environments.client.hot.send({ type: "full-reload" })
|
|
229
|
-
}
|
|
230
|
-
|
|
231
190
|
/** Resolve livemark.config.ts's `vite` field — either a config object
|
|
232
191
|
* or a function `(env) => config` (matching Vite's own `defineConfig`).
|
|
233
192
|
* Schema stores it as `unknown`; typing for the consumer's editor
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { existsSync } from "node:fs"
|
|
2
2
|
import { join } from "node:path"
|
|
3
3
|
import type { Plugin } from "vite"
|
|
4
|
-
import type { Config } from "../models/config.ts"
|
|
4
|
+
import type { Config, ContentSignals } from "../models/config.ts"
|
|
5
5
|
|
|
6
6
|
export interface RobotsOptions {
|
|
7
7
|
config: Config
|
|
@@ -35,8 +35,13 @@ export function robots(opts: RobotsOptions): Plugin {
|
|
|
35
35
|
* line is appended only when `site` is set, because the directive
|
|
36
36
|
* requires a fully-qualified URL; `base` is included so the URL matches
|
|
37
37
|
* where the build (and the `sitemap.xml` inside it) is actually mounted. */
|
|
38
|
-
export function renderRobots(
|
|
39
|
-
|
|
38
|
+
export function renderRobots(
|
|
39
|
+
config: Pick<Config, "site" | "base"> & { contentSignals?: ContentSignals },
|
|
40
|
+
) {
|
|
41
|
+
const lines = ["User-agent: *"]
|
|
42
|
+
// Agents read the signal as part of a user-agent group, so it must sit inside one.
|
|
43
|
+
if (config.contentSignals) lines.push(renderContentSignal(config.contentSignals))
|
|
44
|
+
lines.push("Allow: /")
|
|
40
45
|
if (config.site) {
|
|
41
46
|
const host = config.site.replace(/\/+$/, "")
|
|
42
47
|
const base = config.base ?? ""
|
|
@@ -44,3 +49,13 @@ export function renderRobots(config: Pick<Config, "site" | "base">) {
|
|
|
44
49
|
}
|
|
45
50
|
return `${lines.join("\n")}\n`
|
|
46
51
|
}
|
|
52
|
+
|
|
53
|
+
function renderContentSignal(signals: ContentSignals) {
|
|
54
|
+
const flag = (value: boolean) => (value ? "yes" : "no")
|
|
55
|
+
const pairs = [
|
|
56
|
+
`search=${flag(signals.search)}`,
|
|
57
|
+
`ai-input=${flag(signals.aiInput)}`,
|
|
58
|
+
`ai-train=${flag(signals.aiTrain)}`,
|
|
59
|
+
]
|
|
60
|
+
return `Content-Signal: ${pairs.join(", ")}`
|
|
61
|
+
}
|
|
@@ -27,4 +27,11 @@ describe("renderRobots", () => {
|
|
|
27
27
|
it("ignores base when site is not set", () => {
|
|
28
28
|
expect(renderRobots({ base: "/repo" })).toBe("User-agent: *\nAllow: /\n")
|
|
29
29
|
})
|
|
30
|
+
|
|
31
|
+
it("puts the content signals inside the wildcard group", () => {
|
|
32
|
+
const contentSignals = { search: true, aiInput: true, aiTrain: false }
|
|
33
|
+
expect(renderRobots({ contentSignals })).toBe(
|
|
34
|
+
"User-agent: *\nContent-Signal: search=yes, ai-input=yes, ai-train=no\nAllow: /\n",
|
|
35
|
+
)
|
|
36
|
+
})
|
|
30
37
|
})
|
package/source/program.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Command } from "commander"
|
|
2
2
|
import { build } from "./commands/build.ts"
|
|
3
|
+
import { clear } from "./commands/clear.ts"
|
|
3
4
|
import { escape } from "./commands/escape.ts"
|
|
4
5
|
import { preview } from "./commands/preview.ts"
|
|
5
6
|
import { start } from "./commands/start.ts"
|
|
@@ -16,3 +17,4 @@ export const program = new Command()
|
|
|
16
17
|
.addCommand(build)
|
|
17
18
|
.addCommand(preview)
|
|
18
19
|
.addCommand(escape)
|
|
20
|
+
.addCommand(clear)
|
package/source/routes/$.tsx
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
+
import { config } from "livemark:virtual"
|
|
1
2
|
import { createFileRoute, notFound, redirect } from "@tanstack/react-router"
|
|
3
|
+
import { z } from "zod"
|
|
2
4
|
import {
|
|
3
5
|
currentSection,
|
|
4
6
|
firstArticle,
|
|
@@ -6,12 +8,16 @@ import {
|
|
|
6
8
|
sortedArticles,
|
|
7
9
|
} from "../content/article.ts"
|
|
8
10
|
import { sectionTags } from "../content/tag.ts"
|
|
11
|
+
import { buildPageHead } from "../helpers/head.ts"
|
|
9
12
|
import { Article } from "../components/Article.tsx"
|
|
10
13
|
import { BlogIndex } from "../components/BlogIndex.tsx"
|
|
11
14
|
import { ChangelogIndex } from "../components/ChangelogIndex.tsx"
|
|
15
|
+
import { Layout } from "../components/Layout.tsx"
|
|
12
16
|
import { TagIndex } from "../components/TagIndex.tsx"
|
|
13
17
|
|
|
14
18
|
export const Route = createFileRoute("/$")({
|
|
19
|
+
// Loose, so keys this route does not own (UTM tags, devtools flags) survive.
|
|
20
|
+
validateSearch: z.looseObject({ highlight: z.string().optional().catch(undefined) }),
|
|
15
21
|
loader: ({ params }) => {
|
|
16
22
|
if (!params._splat) {
|
|
17
23
|
if (homeArticle) return { ...homeArticle, sidebar: homeArticle.sidebar }
|
|
@@ -62,19 +68,31 @@ export const Route = createFileRoute("/$")({
|
|
|
62
68
|
|
|
63
69
|
throw notFound()
|
|
64
70
|
},
|
|
65
|
-
head: ({ loaderData }) =>
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
71
|
+
head: ({ loaderData, params }) =>
|
|
72
|
+
buildPageHead({
|
|
73
|
+
config,
|
|
74
|
+
path: params._splat ? `/${params._splat}/` : "/",
|
|
75
|
+
title: loaderData?.title,
|
|
76
|
+
description:
|
|
77
|
+
loaderData && "description" in loaderData ? loaderData.description : undefined,
|
|
78
|
+
image: loaderData && "image" in loaderData ? loaderData.image : undefined,
|
|
79
|
+
isArticle: loaderData && "file" in loaderData,
|
|
80
|
+
}),
|
|
73
81
|
component: Component,
|
|
74
82
|
})
|
|
75
83
|
|
|
76
84
|
function Component() {
|
|
77
85
|
const data = Route.useLoaderData()
|
|
86
|
+
return (
|
|
87
|
+
<Layout withSidebar={data.sidebar}>
|
|
88
|
+
<Page />
|
|
89
|
+
</Layout>
|
|
90
|
+
)
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function Page() {
|
|
94
|
+
const data = Route.useLoaderData()
|
|
95
|
+
const search = Route.useSearch()
|
|
78
96
|
if ("blogIndex" in data) {
|
|
79
97
|
return <BlogIndex sectionPrefix={data.sectionPrefix} />
|
|
80
98
|
}
|
|
@@ -84,5 +102,5 @@ function Component() {
|
|
|
84
102
|
if ("tagPage" in data) {
|
|
85
103
|
return <TagIndex sectionPrefix={data.sectionPrefix} tag={data.tag} />
|
|
86
104
|
}
|
|
87
|
-
return <Article article={data} />
|
|
105
|
+
return <Article article={data} highlight={search.highlight} />
|
|
88
106
|
}
|