@takazudo/zudo-doc 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.
Files changed (255) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +18 -0
  3. package/dist/body-foot-util/body-foot-util-area.d.ts +55 -0
  4. package/dist/body-foot-util/body-foot-util-area.js +71 -0
  5. package/dist/body-foot-util/edit-link.d.ts +45 -0
  6. package/dist/body-foot-util/edit-link.js +42 -0
  7. package/dist/body-foot-util/index.d.ts +3 -0
  8. package/dist/body-foot-util/index.js +11 -0
  9. package/dist/breadcrumb/breadcrumb.d.ts +57 -0
  10. package/dist/breadcrumb/breadcrumb.js +113 -0
  11. package/dist/breadcrumb/find-path.d.ts +16 -0
  12. package/dist/breadcrumb/find-path.js +13 -0
  13. package/dist/breadcrumb/index.d.ts +4 -0
  14. package/dist/breadcrumb/index.js +7 -0
  15. package/dist/breadcrumb/types.d.ts +32 -0
  16. package/dist/breadcrumb/types.js +0 -0
  17. package/dist/code-syntax/code-block-enhancer-script.d.ts +3 -0
  18. package/dist/code-syntax/code-block-enhancer-script.js +165 -0
  19. package/dist/code-syntax/code-block-enhancer.d.ts +22 -0
  20. package/dist/code-syntax/code-block-enhancer.js +13 -0
  21. package/dist/code-syntax/index.d.ts +8 -0
  22. package/dist/code-syntax/index.js +29 -0
  23. package/dist/code-syntax/mermaid-init-script.d.ts +51 -0
  24. package/dist/code-syntax/mermaid-init-script.js +225 -0
  25. package/dist/code-syntax/mermaid-init.d.ts +48 -0
  26. package/dist/code-syntax/mermaid-init.js +14 -0
  27. package/dist/code-syntax/tabs-init-script.d.ts +3 -0
  28. package/dist/code-syntax/tabs-init-script.js +106 -0
  29. package/dist/code-syntax/tabs-init.d.ts +24 -0
  30. package/dist/code-syntax/tabs-init.js +10 -0
  31. package/dist/code-syntax/tabs.d.ts +36 -0
  32. package/dist/code-syntax/tabs.js +62 -0
  33. package/dist/content/component-map.d.ts +51 -0
  34. package/dist/content/component-map.js +29 -0
  35. package/dist/content/content-blockquote.d.ts +11 -0
  36. package/dist/content/content-blockquote.js +14 -0
  37. package/dist/content/content-code.d.ts +26 -0
  38. package/dist/content/content-code.js +39 -0
  39. package/dist/content/content-link.d.ts +12 -0
  40. package/dist/content/content-link.js +43 -0
  41. package/dist/content/content-ol.d.ts +11 -0
  42. package/dist/content/content-ol.js +15 -0
  43. package/dist/content/content-paragraph.d.ts +10 -0
  44. package/dist/content/content-paragraph.js +7 -0
  45. package/dist/content/content-strong.d.ts +11 -0
  46. package/dist/content/content-strong.js +14 -0
  47. package/dist/content/content-table.d.ts +11 -0
  48. package/dist/content/content-table.js +14 -0
  49. package/dist/content/content-ul.d.ts +11 -0
  50. package/dist/content/content-ul.js +15 -0
  51. package/dist/content/heading-h2.d.ts +14 -0
  52. package/dist/content/heading-h2.js +18 -0
  53. package/dist/content/heading-h3.d.ts +14 -0
  54. package/dist/content/heading-h3.js +18 -0
  55. package/dist/content/heading-h4.d.ts +14 -0
  56. package/dist/content/heading-h4.js +18 -0
  57. package/dist/content/index.d.ts +13 -0
  58. package/dist/content/index.js +27 -0
  59. package/dist/details/details.d.ts +26 -0
  60. package/dist/details/details.js +10 -0
  61. package/dist/details/index.d.ts +2 -0
  62. package/dist/details/index.js +4 -0
  63. package/dist/doclayout/anchors.d.ts +55 -0
  64. package/dist/doclayout/anchors.js +42 -0
  65. package/dist/doclayout/doc-layout-with-defaults.d.ts +83 -0
  66. package/dist/doclayout/doc-layout-with-defaults.js +159 -0
  67. package/dist/doclayout/doc-layout.d.ts +121 -0
  68. package/dist/doclayout/doc-layout.js +89 -0
  69. package/dist/doclayout/index.d.ts +6 -0
  70. package/dist/doclayout/index.js +17 -0
  71. package/dist/footer/footer.d.ts +42 -0
  72. package/dist/footer/footer.js +79 -0
  73. package/dist/footer/index.d.ts +3 -0
  74. package/dist/footer/index.js +4 -0
  75. package/dist/footer/types.d.ts +58 -0
  76. package/dist/footer/types.js +0 -0
  77. package/dist/head/doc-head.d.ts +33 -0
  78. package/dist/head/doc-head.js +90 -0
  79. package/dist/head/index.d.ts +5 -0
  80. package/dist/head/index.js +8 -0
  81. package/dist/head/og-tags.d.ts +25 -0
  82. package/dist/head/og-tags.js +16 -0
  83. package/dist/head/twitter-card.d.ts +21 -0
  84. package/dist/head/twitter-card.js +14 -0
  85. package/dist/head/types.d.ts +83 -0
  86. package/dist/head/types.js +0 -0
  87. package/dist/header/header.d.ts +158 -0
  88. package/dist/header/header.js +367 -0
  89. package/dist/header/index.d.ts +6 -0
  90. package/dist/header/index.js +18 -0
  91. package/dist/header/nav-active.d.ts +39 -0
  92. package/dist/header/nav-active.js +24 -0
  93. package/dist/header/nav-overflow-script.d.ts +3 -0
  94. package/dist/header/nav-overflow-script.js +169 -0
  95. package/dist/header/right-items.d.ts +38 -0
  96. package/dist/header/right-items.js +30 -0
  97. package/dist/header/types.d.ts +40 -0
  98. package/dist/header/types.js +0 -0
  99. package/dist/html-preview-wrapper/dedent.d.ts +10 -0
  100. package/dist/html-preview-wrapper/dedent.js +16 -0
  101. package/dist/html-preview-wrapper/highlighted-code.d.ts +18 -0
  102. package/dist/html-preview-wrapper/highlighted-code.js +53 -0
  103. package/dist/html-preview-wrapper/html-preview-wrapper.d.ts +60 -0
  104. package/dist/html-preview-wrapper/html-preview-wrapper.js +36 -0
  105. package/dist/html-preview-wrapper/html-preview.d.ts +35 -0
  106. package/dist/html-preview-wrapper/html-preview.js +75 -0
  107. package/dist/html-preview-wrapper/index.d.ts +5 -0
  108. package/dist/html-preview-wrapper/index.js +12 -0
  109. package/dist/html-preview-wrapper/preflight.d.ts +13 -0
  110. package/dist/html-preview-wrapper/preflight.js +108 -0
  111. package/dist/html-preview-wrapper/preview-base.d.ts +29 -0
  112. package/dist/html-preview-wrapper/preview-base.js +127 -0
  113. package/dist/i18n-version/index.d.ts +5 -0
  114. package/dist/i18n-version/index.js +14 -0
  115. package/dist/i18n-version/language-switcher.d.ts +24 -0
  116. package/dist/i18n-version/language-switcher.js +25 -0
  117. package/dist/i18n-version/types.d.ts +43 -0
  118. package/dist/i18n-version/types.js +0 -0
  119. package/dist/i18n-version/version-banner.d.ts +30 -0
  120. package/dist/i18n-version/version-banner.js +21 -0
  121. package/dist/i18n-version/version-switcher.d.ts +112 -0
  122. package/dist/i18n-version/version-switcher.js +157 -0
  123. package/dist/index.d.ts +2 -0
  124. package/dist/index.js +0 -0
  125. package/dist/integrations/claude-resources/escape-for-mdx.d.ts +8 -0
  126. package/dist/integrations/claude-resources/escape-for-mdx.js +131 -0
  127. package/dist/integrations/claude-resources/generate.d.ts +13 -0
  128. package/dist/integrations/claude-resources/generate.js +404 -0
  129. package/dist/integrations/claude-resources/index.d.ts +46 -0
  130. package/dist/integrations/claude-resources/index.js +24 -0
  131. package/dist/integrations/doc-history/index.d.ts +137 -0
  132. package/dist/integrations/doc-history/index.js +138 -0
  133. package/dist/integrations/doc-history/pre-build.d.ts +58 -0
  134. package/dist/integrations/doc-history/pre-build.js +67 -0
  135. package/dist/integrations/llms-txt/dev-middleware.d.ts +40 -0
  136. package/dist/integrations/llms-txt/dev-middleware.js +76 -0
  137. package/dist/integrations/llms-txt/emit.d.ts +22 -0
  138. package/dist/integrations/llms-txt/emit.js +55 -0
  139. package/dist/integrations/llms-txt/generate.d.ts +26 -0
  140. package/dist/integrations/llms-txt/generate.js +36 -0
  141. package/dist/integrations/llms-txt/index.d.ts +7 -0
  142. package/dist/integrations/llms-txt/index.js +27 -0
  143. package/dist/integrations/llms-txt/load.d.ts +51 -0
  144. package/dist/integrations/llms-txt/load.js +83 -0
  145. package/dist/integrations/llms-txt/strip.d.ts +22 -0
  146. package/dist/integrations/llms-txt/strip.js +10 -0
  147. package/dist/integrations/llms-txt/types.d.ts +145 -0
  148. package/dist/integrations/llms-txt/types.js +0 -0
  149. package/dist/integrations/search-index/build-emitter.d.ts +30 -0
  150. package/dist/integrations/search-index/build-emitter.js +15 -0
  151. package/dist/integrations/search-index/collect.d.ts +12 -0
  152. package/dist/integrations/search-index/collect.js +48 -0
  153. package/dist/integrations/search-index/content-files.d.ts +32 -0
  154. package/dist/integrations/search-index/content-files.js +50 -0
  155. package/dist/integrations/search-index/dev-middleware.d.ts +16 -0
  156. package/dist/integrations/search-index/dev-middleware.js +30 -0
  157. package/dist/integrations/search-index/index.d.ts +5 -0
  158. package/dist/integrations/search-index/index.js +14 -0
  159. package/dist/integrations/search-index/types.d.ts +38 -0
  160. package/dist/integrations/search-index/types.js +6 -0
  161. package/dist/metainfo/doc-metainfo.d.ts +50 -0
  162. package/dist/metainfo/doc-metainfo.js +107 -0
  163. package/dist/metainfo/doc-tags.d.ts +66 -0
  164. package/dist/metainfo/doc-tags.js +53 -0
  165. package/dist/metainfo/frontmatter-preview.d.ts +95 -0
  166. package/dist/metainfo/frontmatter-preview.js +55 -0
  167. package/dist/metainfo/index.d.ts +4 -0
  168. package/dist/metainfo/index.js +28 -0
  169. package/dist/nav-indexing/category-nav.d.ts +28 -0
  170. package/dist/nav-indexing/category-nav.js +41 -0
  171. package/dist/nav-indexing/category-tree-nav.d.ts +27 -0
  172. package/dist/nav-indexing/category-tree-nav.js +49 -0
  173. package/dist/nav-indexing/doc-card-grid.d.ts +38 -0
  174. package/dist/nav-indexing/doc-card-grid.js +40 -0
  175. package/dist/nav-indexing/docs-sitemap.d.ts +25 -0
  176. package/dist/nav-indexing/docs-sitemap.js +40 -0
  177. package/dist/nav-indexing/index.d.ts +10 -0
  178. package/dist/nav-indexing/index.js +18 -0
  179. package/dist/nav-indexing/nav-card-grid.d.ts +31 -0
  180. package/dist/nav-indexing/nav-card-grid.js +43 -0
  181. package/dist/nav-indexing/site-tree-nav-demo.d.ts +39 -0
  182. package/dist/nav-indexing/site-tree-nav-demo.js +65 -0
  183. package/dist/nav-indexing/tag-nav.d.ts +38 -0
  184. package/dist/nav-indexing/tag-nav.js +96 -0
  185. package/dist/nav-indexing/types.d.ts +95 -0
  186. package/dist/nav-indexing/types.js +0 -0
  187. package/dist/nav-indexing/versions-page-content.d.ts +29 -0
  188. package/dist/nav-indexing/versions-page-content.js +76 -0
  189. package/dist/page-loading/index.d.ts +2 -0
  190. package/dist/page-loading/index.js +10 -0
  191. package/dist/page-loading/page-loading-overlay.d.ts +35 -0
  192. package/dist/page-loading/page-loading-overlay.js +50 -0
  193. package/dist/sidebar/index.d.ts +3 -0
  194. package/dist/sidebar/index.js +4 -0
  195. package/dist/sidebar/sidebar.d.ts +54 -0
  196. package/dist/sidebar/sidebar.js +35 -0
  197. package/dist/sidebar/types.d.ts +71 -0
  198. package/dist/sidebar/types.js +0 -0
  199. package/dist/sidebar-resizer/index.d.ts +14 -0
  200. package/dist/sidebar-resizer/index.js +165 -0
  201. package/dist/sidebar-resizer/sidebar-resizer-init.d.ts +20 -0
  202. package/dist/sidebar-resizer/sidebar-resizer-init.js +119 -0
  203. package/dist/sidebar-tree/build-tree.d.ts +43 -0
  204. package/dist/sidebar-tree/build-tree.js +127 -0
  205. package/dist/sidebar-tree/category-meta.d.ts +19 -0
  206. package/dist/sidebar-tree/category-meta.js +63 -0
  207. package/dist/sidebar-tree/index.d.ts +3 -0
  208. package/dist/sidebar-tree/index.js +13 -0
  209. package/dist/sidebar-tree/types.d.ts +120 -0
  210. package/dist/sidebar-tree/types.js +0 -0
  211. package/dist/tab-item/index.d.ts +2 -0
  212. package/dist/tab-item/index.js +5 -0
  213. package/dist/tab-item/tab-item.d.ts +38 -0
  214. package/dist/tab-item/tab-item.js +25 -0
  215. package/dist/theme/color-scheme-provider.d.ts +32 -0
  216. package/dist/theme/color-scheme-provider.js +43 -0
  217. package/dist/theme/color-tweak-export-modal.d.ts +20 -0
  218. package/dist/theme/color-tweak-export-modal.js +138 -0
  219. package/dist/theme/design-token-serde.d.ts +136 -0
  220. package/dist/theme/design-token-serde.js +254 -0
  221. package/dist/theme/design-token-tweak-panel.d.ts +11 -0
  222. package/dist/theme/design-token-tweak-panel.js +10 -0
  223. package/dist/theme/design-token-types.d.ts +19 -0
  224. package/dist/theme/design-token-types.js +6 -0
  225. package/dist/theme/iframe-bridge.d.ts +44 -0
  226. package/dist/theme/iframe-bridge.js +70 -0
  227. package/dist/theme/index.d.ts +9 -0
  228. package/dist/theme/index.js +42 -0
  229. package/dist/theme/theme-toggle.d.ts +17 -0
  230. package/dist/theme/theme-toggle.js +91 -0
  231. package/dist/toc/cx.d.ts +16 -0
  232. package/dist/toc/cx.js +22 -0
  233. package/dist/toc/index.d.ts +5 -0
  234. package/dist/toc/index.js +9 -0
  235. package/dist/toc/mobile-toc.d.ts +46 -0
  236. package/dist/toc/mobile-toc.js +88 -0
  237. package/dist/toc/smart-break.d.ts +27 -0
  238. package/dist/toc/smart-break.js +36 -0
  239. package/dist/toc/toc-title.d.ts +10 -0
  240. package/dist/toc/toc-title.js +14 -0
  241. package/dist/toc/toc.d.ts +60 -0
  242. package/dist/toc/toc.js +56 -0
  243. package/dist/toc/types.d.ts +23 -0
  244. package/dist/toc/types.js +0 -0
  245. package/dist/toc/use-active-heading.d.ts +30 -0
  246. package/dist/toc/use-active-heading.js +106 -0
  247. package/dist/transitions/index.d.ts +1 -0
  248. package/dist/transitions/index.js +12 -0
  249. package/dist/transitions/page-events.d.ts +41 -0
  250. package/dist/transitions/page-events.js +20 -0
  251. package/dist/url-normalizer/index.d.ts +1 -0
  252. package/dist/url-normalizer/index.js +6 -0
  253. package/dist/url-normalizer/normalize.d.ts +104 -0
  254. package/dist/url-normalizer/normalize.js +22 -0
  255. package/package.json +186 -0
@@ -0,0 +1,83 @@
1
+ import { readFileSync, readdirSync } from "node:fs";
2
+ import { join, relative, resolve } from "node:path";
3
+ import matter from "gray-matter";
4
+ import { stripImportsAndJsx, stripMarkdown } from "./strip.js";
5
+ function collectMdFiles(dir) {
6
+ const results = [];
7
+ function walk(currentDir, baseDir) {
8
+ let entries;
9
+ try {
10
+ entries = readdirSync(currentDir, { withFileTypes: true });
11
+ } catch {
12
+ return;
13
+ }
14
+ for (const entry of entries) {
15
+ const fullPath = join(currentDir, entry.name);
16
+ if (entry.isDirectory()) {
17
+ walk(fullPath, baseDir);
18
+ } else if (/\.mdx?$/.test(entry.name) && !entry.name.startsWith("_")) {
19
+ const rel = relative(baseDir, fullPath).replace(/\.mdx?$/, "").replace(/\/index$/, "");
20
+ results.push({ filePath: fullPath, slug: rel });
21
+ }
22
+ }
23
+ }
24
+ walk(dir, dir);
25
+ return results;
26
+ }
27
+ function slugToUrl(slug, locale, base, siteUrl) {
28
+ const trimmedBase = base.replace(/\/$/, "");
29
+ const path = locale ? `${trimmedBase}/${locale}/docs/${slug}` : `${trimmedBase}/docs/${slug}`;
30
+ if (siteUrl) {
31
+ return `${siteUrl.replace(/\/$/, "")}${path}`;
32
+ }
33
+ return path;
34
+ }
35
+ function isExcluded(data) {
36
+ return Boolean(data.search_exclude || data.draft || data.unlisted);
37
+ }
38
+ function parseMarkdownFile(filePath) {
39
+ try {
40
+ const raw = readFileSync(filePath, "utf-8");
41
+ const parsed = matter(raw);
42
+ return { data: parsed.data, content: parsed.content };
43
+ } catch {
44
+ return null;
45
+ }
46
+ }
47
+ function loadDocEntries(options) {
48
+ const { contentDir, locale, base, siteUrl } = options;
49
+ const absDir = resolve(contentDir);
50
+ const files = collectMdFiles(absDir);
51
+ const entries = [];
52
+ for (const { filePath, slug } of files) {
53
+ const parsed = parseMarkdownFile(filePath);
54
+ if (!parsed) continue;
55
+ const { data, content } = parsed;
56
+ if (isExcluded(data)) continue;
57
+ let description = data.description ?? "";
58
+ if (!description) {
59
+ const stripped = stripMarkdown(content);
60
+ description = stripped.split("\n").find((l) => l.trim().length > 0) ?? "";
61
+ }
62
+ entries.push({
63
+ title: data.title ?? slug,
64
+ description,
65
+ url: slugToUrl(slug, locale, base, siteUrl),
66
+ content: stripImportsAndJsx(content),
67
+ sidebarPosition: data.sidebar_position
68
+ });
69
+ }
70
+ entries.sort((a, b) => {
71
+ const posA = a.sidebarPosition ?? Number.MAX_SAFE_INTEGER;
72
+ const posB = b.sidebarPosition ?? Number.MAX_SAFE_INTEGER;
73
+ return posA - posB;
74
+ });
75
+ return entries;
76
+ }
77
+ export {
78
+ collectMdFiles,
79
+ isExcluded,
80
+ loadDocEntries,
81
+ parseMarkdownFile,
82
+ slugToUrl
83
+ };
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Markdown / JSX text-stripping helpers.
3
+ *
4
+ * The two functions below match the legacy Astro emitter's behaviour
5
+ * byte-for-byte. Any change here is a behaviour change and must be
6
+ * reflected in the byte-equality fixture corpus.
7
+ */
8
+ /**
9
+ * Strip imports, exports, and HTML/JSX tags from MDX/MD content while
10
+ * keeping the prose layout (headings, lists, blockquotes) intact for
11
+ * LLM consumption. Used by `llms-full.txt`.
12
+ */
13
+ declare function stripImportsAndJsx(content: string): string;
14
+ /**
15
+ * Strip markdown formatting and produce plain prose. Used to derive a
16
+ * fallback `description` from the body when frontmatter doesn't carry
17
+ * one. The regex sequence matches the legacy `stripMarkdown` helper in
18
+ * `src/utils/content-files.ts` — keep them in sync.
19
+ */
20
+ declare function stripMarkdown(md: string): string;
21
+
22
+ export { stripImportsAndJsx, stripMarkdown };
@@ -0,0 +1,10 @@
1
+ function stripImportsAndJsx(content) {
2
+ return content.replace(/^import\s+.*$/gm, "").replace(/^export\s+.*$/gm, "").replace(/<\/?[a-zA-Z][a-zA-Z0-9]*[^>]*>/g, "").replace(/\n{3,}/g, "\n\n").trim();
3
+ }
4
+ function stripMarkdown(md) {
5
+ return md.replace(/```[\s\S]*?```/g, "").replace(/`[^`]+`/g, "").replace(/<[^>]+>/g, "").replace(/^#{1,6}\s+/gm, "").replace(/\*{1,3}([^*]+)\*{1,3}/g, "$1").replace(/_{1,3}([^_]+)_{1,3}/g, "$1").replace(/!\[[^\]]*\]\([^)]+\)/g, "").replace(/\[([^\]]+)\]\([^)]+\)/g, "$1").replace(/^>\s+/gm, "").replace(/^[-*_]{3,}\s*$/gm, "").replace(/^[\s]*[-*+]\s+/gm, "").replace(/^[\s]*\d+\.\s+/gm, "").replace(/^import\s+.*$/gm, "").replace(/^export\s+.*$/gm, "").replace(/\n{3,}/g, "\n\n").trim();
6
+ }
7
+ export {
8
+ stripImportsAndJsx,
9
+ stripMarkdown
10
+ };
@@ -0,0 +1,145 @@
1
+ /**
2
+ * Public types for the framework-agnostic llms-txt build emitter.
3
+ *
4
+ * The shapes here are deliberately decoupled from `astro:content` and
5
+ * Astro's `AstroIntegration` API so the emitter can run on top of any
6
+ * pipeline that exposes a `dist/` output directory and a list of
7
+ * markdown content roots — Astro today, zfb tomorrow, plain Node tooling
8
+ * in a unit test, etc.
9
+ */
10
+ /**
11
+ * Frontmatter fields the loader reads off of each MDX/MD file. Callers
12
+ * are free to extend this; unrecognised fields are ignored.
13
+ */
14
+ interface LlmsTxtFrontmatter {
15
+ title?: string;
16
+ description?: string;
17
+ sidebar_position?: number;
18
+ draft?: boolean;
19
+ unlisted?: boolean;
20
+ /**
21
+ * `true` when the page is excluded from search and llms.txt indexing.
22
+ * Mirrors the `search_exclude` flag used by the existing project.
23
+ */
24
+ search_exclude?: boolean;
25
+ [key: string]: unknown;
26
+ }
27
+ /**
28
+ * One entry in the generated llms.txt / llms-full.txt files. Pure data,
29
+ * already sorted, already stripped — generators (`generateLlmsTxt`,
30
+ * `generateLlmsFullTxt`) consume this without further processing.
31
+ */
32
+ interface LlmsDocEntry {
33
+ /** Page title (frontmatter `title`, with the slug as a fallback). */
34
+ title: string;
35
+ /**
36
+ * Short description used as the bullet's right-hand side in
37
+ * `llms.txt`. Falls back to the first non-blank prose line of the
38
+ * stripped body when `frontmatter.description` is absent.
39
+ */
40
+ description: string;
41
+ /**
42
+ * Public URL for the page. Absolute when `siteUrl` is supplied to the
43
+ * loader, otherwise a path-only URL beginning with `base`.
44
+ */
45
+ url: string;
46
+ /**
47
+ * Body content with imports/exports/JSX tags removed. Used verbatim
48
+ * inside `llms-full.txt`.
49
+ */
50
+ content: string;
51
+ /** `frontmatter.sidebar_position`, undefined when not declared. */
52
+ sidebarPosition: number | undefined;
53
+ }
54
+ /**
55
+ * Site-level metadata threaded through the generators. Matches the
56
+ * `siteName` / `siteDescription` fields on the legacy settings object
57
+ * one-for-one so callers can pass `settings` straight through.
58
+ */
59
+ interface LlmsTxtSiteMeta {
60
+ siteName: string;
61
+ siteDescription: string;
62
+ }
63
+ /**
64
+ * One locale's content directory + URL prefix. The default locale is
65
+ * represented separately on {@link LlmsTxtEmitOptions}; this type
66
+ * describes the *additional* locales (e.g. `ja`).
67
+ */
68
+ interface LlmsTxtLocaleConfig {
69
+ /** Locale code used as both the URL segment and the output subfolder. */
70
+ code: string;
71
+ /** Absolute or cwd-relative path to the locale's content root. */
72
+ dir: string;
73
+ }
74
+ /**
75
+ * Options for {@link loadDocEntries} — one content directory at a time.
76
+ */
77
+ interface LlmsTxtLoadOptions {
78
+ /** Absolute or cwd-relative path to the markdown content root. */
79
+ contentDir: string;
80
+ /**
81
+ * Locale segment for the generated URLs. Pass `null` to omit the
82
+ * locale segment (the default locale).
83
+ */
84
+ locale: string | null;
85
+ /**
86
+ * URL base, with or without a trailing slash. The loader strips the
87
+ * trailing slash before joining.
88
+ */
89
+ base: string;
90
+ /**
91
+ * Optional absolute site URL. When set, the loader produces fully
92
+ * qualified URLs by prefixing the base+path with `siteUrl`. When
93
+ * empty, URLs are returned path-only (matching the legacy emitter's
94
+ * behaviour for projects that omit `siteUrl`).
95
+ */
96
+ siteUrl?: string;
97
+ }
98
+ /**
99
+ * Top-level options for {@link emitLlmsTxt}. Mirrors the inputs the
100
+ * legacy Astro emitter pulled implicitly off of the project's `settings`
101
+ * module so that a one-line registration in `zfb.config.ts` (or a
102
+ * `b4-build` hook in Astro) can wire the same behaviour.
103
+ */
104
+ interface LlmsTxtEmitOptions extends LlmsTxtSiteMeta {
105
+ /** Absolute output directory — typically `dist/`. */
106
+ outDir: string;
107
+ /** URL base used when materialising per-page links. */
108
+ base: string;
109
+ /** Optional absolute site URL; see {@link LlmsTxtLoadOptions}. */
110
+ siteUrl?: string;
111
+ /**
112
+ * Default-locale content directory. Emitted at `outDir/llms.txt` and
113
+ * `outDir/llms-full.txt`.
114
+ */
115
+ defaultLocaleDir: string;
116
+ /**
117
+ * Additional locales. Each one emits to
118
+ * `outDir/<code>/llms.txt` and `outDir/<code>/llms-full.txt`.
119
+ */
120
+ locales?: LlmsTxtLocaleConfig[];
121
+ /**
122
+ * Optional logger. Defaults to a no-op so unit tests stay quiet; the
123
+ * Astro adapter can wire `astro:build:done`'s `logger` through, and
124
+ * a future zfb plugin can pipe its own.
125
+ */
126
+ logger?: LlmsTxtLogger;
127
+ }
128
+ /**
129
+ * Minimal logger interface — a structural subset of both Astro's
130
+ * `logger` and `console`. Only `info` is used today; the wider shape is
131
+ * provided so the option can hold either object without type acrobatics.
132
+ */
133
+ interface LlmsTxtLogger {
134
+ info: (msg: string) => void;
135
+ }
136
+ /**
137
+ * Result returned by {@link emitLlmsTxt}. Useful for tests and for
138
+ * callers that want to log / further process the emitted set.
139
+ */
140
+ interface LlmsTxtEmitResult {
141
+ /** Absolute paths of every file written, in emit order. */
142
+ written: string[];
143
+ }
144
+
145
+ export type { LlmsDocEntry, LlmsTxtEmitOptions, LlmsTxtEmitResult, LlmsTxtFrontmatter, LlmsTxtLoadOptions, LlmsTxtLocaleConfig, LlmsTxtLogger, LlmsTxtSiteMeta };
File without changes
@@ -0,0 +1,30 @@
1
+ import { SearchIndexConfig, SearchIndexEntry } from './types.js';
2
+
3
+ interface SearchIndexBuildResult {
4
+ /** Absolute path of the JSON file that was written. */
5
+ jsonPath: string;
6
+ /** Number of entries serialised. */
7
+ entryCount: number;
8
+ /** The raw entries — exposed so callers can pipe the same data into a Worker bundle if needed. */
9
+ entries: SearchIndexEntry[];
10
+ }
11
+ interface SearchIndexBuildOptions extends SearchIndexConfig {
12
+ /** Absolute path of the build output directory (e.g. zfb's `outDir`). */
13
+ outDir: string;
14
+ /** Optional logger; defaults to a no-op so builds stay quiet by default. */
15
+ logger?: {
16
+ info: (msg: string) => void;
17
+ };
18
+ }
19
+ /**
20
+ * Walk the configured content directories, build the flat search index,
21
+ * and write `search-index.json` to `outDir`. Returns the entries so
22
+ * callers (e.g. a downstream `search-worker` bundler) can reuse the
23
+ * same data without re-walking the filesystem.
24
+ *
25
+ * Schema is byte-identical to today's Astro integration — see
26
+ * `types.ts` for the locked-in shape.
27
+ */
28
+ declare function emitSearchIndex(options: SearchIndexBuildOptions): SearchIndexBuildResult;
29
+
30
+ export { type SearchIndexBuildOptions, type SearchIndexBuildResult, emitSearchIndex };
@@ -0,0 +1,15 @@
1
+ import { mkdirSync, writeFileSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ import { collectSearchEntries } from "./collect.js";
4
+ function emitSearchIndex(options) {
5
+ const { outDir, logger, ...config } = options;
6
+ const entries = collectSearchEntries(config);
7
+ mkdirSync(outDir, { recursive: true });
8
+ const jsonPath = join(outDir, "search-index.json");
9
+ writeFileSync(jsonPath, JSON.stringify(entries));
10
+ logger?.info(`Generated search index with ${entries.length} entries`);
11
+ return { jsonPath, entryCount: entries.length, entries };
12
+ }
13
+ export {
14
+ emitSearchIndex
15
+ };
@@ -0,0 +1,12 @@
1
+ import { SearchIndexConfig, SearchIndexEntry } from './types.js';
2
+
3
+ /**
4
+ * Collect every search-index entry across the default locale plus all
5
+ * configured non-default locales. The traversal order matches today's
6
+ * Astro integration: default locale first, then locales in the iteration
7
+ * order of the `locales` map. Downstream consumers should not rely on
8
+ * order beyond that, but keep it stable for diff-friendly builds.
9
+ */
10
+ declare function collectSearchEntries(config: SearchIndexConfig): SearchIndexEntry[];
11
+
12
+ export { collectSearchEntries };
@@ -0,0 +1,48 @@
1
+ import { resolve } from "node:path";
2
+ import {
3
+ collectMdFiles,
4
+ isExcluded,
5
+ parseMarkdownFile,
6
+ slugToUrl,
7
+ stripMarkdown
8
+ } from "./content-files.js";
9
+ import {
10
+ MAX_BODY_LENGTH
11
+ } from "./types.js";
12
+ function truncateBody(text) {
13
+ return text.length > MAX_BODY_LENGTH ? text.substring(0, MAX_BODY_LENGTH) : text;
14
+ }
15
+ function buildEntries(contentDir, locale, base) {
16
+ const absDir = resolve(contentDir);
17
+ const files = collectMdFiles(absDir);
18
+ const entries = [];
19
+ for (const { filePath, slug } of files) {
20
+ const parsed = parseMarkdownFile(filePath);
21
+ if (!parsed) continue;
22
+ const { data, content } = parsed;
23
+ if (isExcluded(data)) continue;
24
+ const id = locale ? `${locale}/${slug}` : slug;
25
+ entries.push({
26
+ id,
27
+ title: data.title ?? slug,
28
+ body: truncateBody(stripMarkdown(content)),
29
+ url: slugToUrl(slug, locale, base),
30
+ description: data.description ?? ""
31
+ });
32
+ }
33
+ return entries;
34
+ }
35
+ function collectSearchEntries(config) {
36
+ const base = config.base ?? "";
37
+ const entries = [];
38
+ entries.push(...buildEntries(config.docsDir, null, base));
39
+ if (config.locales) {
40
+ for (const [code, locale] of Object.entries(config.locales)) {
41
+ entries.push(...buildEntries(locale.dir, code, base));
42
+ }
43
+ }
44
+ return entries;
45
+ }
46
+ export {
47
+ collectSearchEntries
48
+ };
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Strip markdown formatting to plain text. Conservative-by-design — the
3
+ * regex pipeline is identical to today's Astro integration so search
4
+ * excerpts stay byte-equal across the cutover. Do not add new rules
5
+ * without also updating the byte-equality fixtures (topic-plugin-audit).
6
+ */
7
+ declare function stripMarkdown(md: string): string;
8
+ /** Walk a directory recursively and collect all `.md`/`.mdx` files. */
9
+ declare function collectMdFiles(dir: string): Array<{
10
+ filePath: string;
11
+ slug: string;
12
+ }>;
13
+ /** Compute the site-relative URL for a slug + locale, honouring `base`. */
14
+ declare function slugToUrl(slug: string, locale: string | null, base: string): string;
15
+ /** Frontmatter fields the integration cares about. Extra keys pass through. */
16
+ interface DocFrontmatter {
17
+ title?: string;
18
+ description?: string;
19
+ draft?: boolean;
20
+ unlisted?: boolean;
21
+ search_exclude?: boolean;
22
+ [key: string]: unknown;
23
+ }
24
+ /** Parse a markdown file. Returns null when the file cannot be read. */
25
+ declare function parseMarkdownFile(filePath: string): {
26
+ data: DocFrontmatter;
27
+ content: string;
28
+ } | null;
29
+ /** A page is excluded from indexing when explicitly opted out, drafted, or unlisted. */
30
+ declare function isExcluded(data: DocFrontmatter): boolean;
31
+
32
+ export { type DocFrontmatter, collectMdFiles, isExcluded, parseMarkdownFile, slugToUrl, stripMarkdown };
@@ -0,0 +1,50 @@
1
+ import { readFileSync, readdirSync } from "node:fs";
2
+ import { join, relative } from "node:path";
3
+ import matter from "gray-matter";
4
+ function stripMarkdown(md) {
5
+ return md.replace(/```[\s\S]*?```/g, "").replace(/`[^`]+`/g, "").replace(/<[^>]+>/g, "").replace(/^#{1,6}\s+/gm, "").replace(/\*{1,3}([^*]+)\*{1,3}/g, "$1").replace(/_{1,3}([^_]+)_{1,3}/g, "$1").replace(/!\[[^\]]*\]\([^)]+\)/g, "").replace(/\[([^\]]+)\]\([^)]+\)/g, "$1").replace(/^>\s+/gm, "").replace(/^[-*_]{3,}\s*$/gm, "").replace(/^[\s]*[-*+]\s+/gm, "").replace(/^[\s]*\d+\.\s+/gm, "").replace(/^import\s+.*$/gm, "").replace(/^export\s+.*$/gm, "").replace(/\n{3,}/g, "\n\n").trim();
6
+ }
7
+ function collectMdFiles(dir) {
8
+ const results = [];
9
+ function walk(currentDir, baseDir) {
10
+ let entries;
11
+ try {
12
+ entries = readdirSync(currentDir, { withFileTypes: true });
13
+ } catch {
14
+ return;
15
+ }
16
+ for (const entry of entries) {
17
+ const fullPath = join(currentDir, entry.name);
18
+ if (entry.isDirectory()) {
19
+ walk(fullPath, baseDir);
20
+ } else if (/\.mdx?$/.test(entry.name) && !entry.name.startsWith("_")) {
21
+ const rel = relative(baseDir, fullPath).replace(/\.mdx?$/, "").replace(/\/index$/, "");
22
+ results.push({ filePath: fullPath, slug: rel });
23
+ }
24
+ }
25
+ }
26
+ walk(dir, dir);
27
+ return results;
28
+ }
29
+ function slugToUrl(slug, locale, base) {
30
+ const normalizedBase = base.replace(/\/$/, "");
31
+ return locale ? `${normalizedBase}/${locale}/docs/${slug}` : `${normalizedBase}/docs/${slug}`;
32
+ }
33
+ function parseMarkdownFile(filePath) {
34
+ try {
35
+ const raw = readFileSync(filePath, "utf-8");
36
+ return matter(raw);
37
+ } catch {
38
+ return null;
39
+ }
40
+ }
41
+ function isExcluded(data) {
42
+ return !!(data.search_exclude || data.draft || data.unlisted);
43
+ }
44
+ export {
45
+ collectMdFiles,
46
+ isExcluded,
47
+ parseMarkdownFile,
48
+ slugToUrl,
49
+ stripMarkdown
50
+ };
@@ -0,0 +1,16 @@
1
+ import { IncomingMessage, ServerResponse } from 'node:http';
2
+ import { SearchIndexConfig } from './types.js';
3
+
4
+ type SearchIndexNextFn = (err?: unknown) => void;
5
+ type SearchIndexMiddleware = (req: IncomingMessage, res: ServerResponse, next: SearchIndexNextFn) => void;
6
+ /**
7
+ * Build a dev-server middleware that responds to GET requests for
8
+ * `/search-index.json` (or any URL ending in that suffix, mirroring the
9
+ * tolerant matcher in the Astro integration so a `base` prefix at the
10
+ * dev server does not break the route). Every request rebuilds the
11
+ * index from disk — that matches the Astro behaviour and keeps content
12
+ * edits visible without a dev-server restart.
13
+ */
14
+ declare function createSearchIndexDevMiddleware(config: SearchIndexConfig): SearchIndexMiddleware;
15
+
16
+ export { type SearchIndexMiddleware, type SearchIndexNextFn, createSearchIndexDevMiddleware };
@@ -0,0 +1,30 @@
1
+ import { collectSearchEntries } from "./collect.js";
2
+ import {
3
+ SEARCH_INDEX_ROUTE
4
+ } from "./types.js";
5
+ function createSearchIndexDevMiddleware(config) {
6
+ return (req, res, next) => {
7
+ const url = req.url ?? "";
8
+ const matches = url === SEARCH_INDEX_ROUTE || url.endsWith(SEARCH_INDEX_ROUTE);
9
+ if (!matches) {
10
+ next();
11
+ return;
12
+ }
13
+ try {
14
+ const entries = collectSearchEntries(config);
15
+ res.setHeader("Content-Type", "application/json");
16
+ res.end(JSON.stringify(entries));
17
+ } catch (err) {
18
+ res.statusCode = 500;
19
+ res.setHeader("Content-Type", "application/json");
20
+ res.end(
21
+ JSON.stringify({
22
+ error: err instanceof Error ? err.message : "Internal error"
23
+ })
24
+ );
25
+ }
26
+ };
27
+ }
28
+ export {
29
+ createSearchIndexDevMiddleware
30
+ };
@@ -0,0 +1,5 @@
1
+ export { SearchIndexBuildOptions, SearchIndexBuildResult, emitSearchIndex } from './build-emitter.js';
2
+ export { collectSearchEntries } from './collect.js';
3
+ export { SearchIndexMiddleware, SearchIndexNextFn, createSearchIndexDevMiddleware } from './dev-middleware.js';
4
+ export { MAX_BODY_LENGTH, SEARCH_INDEX_ROUTE, SearchIndexConfig, SearchIndexEntry, SearchIndexLocaleConfig } from './types.js';
5
+ import 'node:http';
@@ -0,0 +1,14 @@
1
+ import { emitSearchIndex } from "./build-emitter.js";
2
+ import { collectSearchEntries } from "./collect.js";
3
+ import { createSearchIndexDevMiddleware } from "./dev-middleware.js";
4
+ import {
5
+ MAX_BODY_LENGTH,
6
+ SEARCH_INDEX_ROUTE
7
+ } from "./types.js";
8
+ export {
9
+ MAX_BODY_LENGTH,
10
+ SEARCH_INDEX_ROUTE,
11
+ collectSearchEntries,
12
+ createSearchIndexDevMiddleware,
13
+ emitSearchIndex
14
+ };
@@ -0,0 +1,38 @@
1
+ /**
2
+ * One page in the search index. The shape mirrors today's Astro
3
+ * integration output verbatim — keep it byte-identical so downstream
4
+ * consumers (search.astro client island and packages/search-worker via
5
+ * MiniSearch) keep working unchanged after the zfb cutover.
6
+ */
7
+ interface SearchIndexEntry {
8
+ /** Stable identifier — `slug` for the default locale, `${locale}/${slug}` otherwise. */
9
+ id: string;
10
+ /** Frontmatter title, falling back to the slug when missing. */
11
+ title: string;
12
+ /** Plain-text body excerpt, capped at MAX_BODY_LENGTH characters. */
13
+ body: string;
14
+ /** Site-relative URL (respecting `base`). */
15
+ url: string;
16
+ /** Frontmatter description, or the empty string when absent. */
17
+ description: string;
18
+ }
19
+ /** A locale entry that mirrors the Astro `settings.locales` shape we already have. */
20
+ interface SearchIndexLocaleConfig {
21
+ /** Absolute or project-relative directory holding this locale's MDX content. */
22
+ dir: string;
23
+ }
24
+ /** Build-time configuration for collecting search entries. */
25
+ interface SearchIndexConfig {
26
+ /** Default-locale content directory (e.g. `src/content/docs`). */
27
+ docsDir: string;
28
+ /** Optional non-default locales, keyed by locale code (e.g. `{ ja: { dir: "src/content/docs-ja" } }`). */
29
+ locales?: Record<string, SearchIndexLocaleConfig>;
30
+ /** Site base path (e.g. `""`, `"/docs"`); trailing slash is normalised away. */
31
+ base?: string;
32
+ }
33
+ /** Maximum body text stored per entry (display excerpt cap). */
34
+ declare const MAX_BODY_LENGTH = 300;
35
+ /** Public route the dev middleware and build emitter agree on. */
36
+ declare const SEARCH_INDEX_ROUTE = "/search-index.json";
37
+
38
+ export { MAX_BODY_LENGTH, SEARCH_INDEX_ROUTE, type SearchIndexConfig, type SearchIndexEntry, type SearchIndexLocaleConfig };
@@ -0,0 +1,6 @@
1
+ const MAX_BODY_LENGTH = 300;
2
+ const SEARCH_INDEX_ROUTE = "/search-index.json";
3
+ export {
4
+ MAX_BODY_LENGTH,
5
+ SEARCH_INDEX_ROUTE
6
+ };
@@ -0,0 +1,50 @@
1
+ import { VNode } from 'preact';
2
+
3
+ /** @jsxRuntime automatic */
4
+ /** @jsxImportSource preact */
5
+
6
+ interface DocMetainfoProps {
7
+ /**
8
+ * Pre-formatted creation date string (e.g. `"Jan 1, 2024"`).
9
+ * The legacy `doc-metainfo.astro` called `formatDate(gitInfo.createdAt,
10
+ * locale)` inline; v2 delegates that computation to the caller.
11
+ * Omit (or pass `null`) to suppress the "created" row.
12
+ */
13
+ createdAt?: string | null;
14
+ /**
15
+ * Pre-formatted last-updated date string. Suppressed when equal to
16
+ * `createdAt` (matching the original template's guard
17
+ * `updatedAt !== createdAt`), or when null / omitted.
18
+ */
19
+ updatedAt?: string | null;
20
+ /**
21
+ * Git author name (or any attribution string). Omit to suppress.
22
+ */
23
+ author?: string | null;
24
+ /**
25
+ * Label for the "created" entry. Defaults to `"Created"`. Pass the
26
+ * i18n-resolved string (`t("doc.created", locale)`) from upstream.
27
+ */
28
+ createdLabel?: string;
29
+ /**
30
+ * Label for the "updated" entry. Defaults to `"Updated"`.
31
+ */
32
+ updatedLabel?: string;
33
+ }
34
+ declare const DEFAULT_CREATED_LABEL = "Created";
35
+ declare const DEFAULT_UPDATED_LABEL = "Updated";
36
+ /**
37
+ * Document meta-information strip (created date, updated date, author)
38
+ * — JSX port of `src/components/doc-metainfo.astro`.
39
+ *
40
+ * The legacy component owned three responsibilities: reading
41
+ * `settings.docMetainfo`, calling `getGitInfo(filePath)`, and
42
+ * presentation. v2 keeps only presentation; the caller resolves git
43
+ * info and date formatting upstream.
44
+ *
45
+ * Returns `null` when no displayable field is provided, mirroring the
46
+ * original `hasInfo && (...)` guard.
47
+ */
48
+ declare function DocMetainfo(props: DocMetainfoProps): VNode | null;
49
+
50
+ export { DEFAULT_CREATED_LABEL, DEFAULT_UPDATED_LABEL, DocMetainfo, type DocMetainfoProps };