@farming-labs/theme 0.1.84 → 0.1.86

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/dist/docs-api.mjs CHANGED
@@ -3,7 +3,7 @@ import { getNextAppDir } from "./get-app-dir.mjs";
3
3
  import fs from "node:fs";
4
4
  import path from "node:path";
5
5
  import matter from "gray-matter";
6
- import { buildDocsAskAIContext, createDocsAgentTraceContext, createDocsAgentTraceId, createDocsSitemapResponse, emitDocsAgentTraceEvent, emitDocsAnalyticsEvent, formatDocsAskAIPackageHints, getDocsMarkdownVaryHeader, hasDocsMarkdownSignatureAgent, normalizeDocsRelated, performDocsSearch, renderDocsMarkdownNotFound, renderDocsRelatedMarkdownLines, resolveAskAISearchRequestConfig, resolveChangelogConfig, resolveDocsI18n, resolveDocsLocale, resolveDocsSitemapConfig, resolvePageSidebarFolderIndexBehavior, resolveSearchRequestConfig } from "@farming-labs/docs";
6
+ import { buildDocsAskAIContext, createDocsAgentTraceContext, createDocsAgentTraceId, createDocsSitemapResponse, emitDocsAgentTraceEvent, emitDocsAnalyticsEvent, formatDocsAskAIPackageHints, getDocsMarkdownVaryHeader, hasDocsMarkdownSignatureAgent, normalizeDocsRelated, performDocsSearch, renderDocsMarkdownNotFound, renderDocsRelatedMarkdownLines, resolveAskAISearchRequestConfig, resolveChangelogConfig, resolveDocsI18n, resolveDocsLocale, resolveDocsSitemapConfig, resolvePageSidebarFolderIndexBehavior, resolveSearchRequestConfig, toDocsMarkdownUrl } from "@farming-labs/docs";
7
7
  import { createDocsMcpHttpHandler, createFilesystemDocsMcpSource, readDocsSitemapManifest, resolveDocsMcpConfig } from "@farming-labs/docs/server";
8
8
 
9
9
  //#region src/docs-api.ts
@@ -211,6 +211,7 @@ function buildAgentSpec({ origin, entry, i18n, search, mcp, feedback, llms, site
211
211
  search: searchEnabled,
212
212
  sitemap: sitemapConfig.enabled,
213
213
  robots: robotsEnabled,
214
+ structuredData: true,
214
215
  agentFeedback: feedback.enabled,
215
216
  locales: localesEnabled
216
217
  },
@@ -265,6 +266,20 @@ function buildAgentSpec({ origin, entry, i18n, search, mcp, feedback, llms, site
265
266
  route: DEFAULT_ROBOTS_TXT_ROUTE,
266
267
  defaultRoute: DEFAULT_ROBOTS_TXT_ROUTE
267
268
  },
269
+ structuredData: {
270
+ enabled: true,
271
+ format: "application/ld+json",
272
+ schema: "https://schema.org/TechArticle",
273
+ fields: [
274
+ "headline",
275
+ "description",
276
+ "url",
277
+ "dateModified",
278
+ "breadcrumb"
279
+ ],
280
+ canonicalUrlField: "url",
281
+ breadcrumbType: "BreadcrumbList"
282
+ },
268
283
  search: {
269
284
  enabled: searchEnabled,
270
285
  endpoint: `${DEFAULT_DOCS_API_ROUTE}?query={query}`,
@@ -1554,7 +1569,7 @@ function generateLlmsTxt(indexes, options) {
1554
1569
  if (siteDescription) llmsTxt += `> ${siteDescription}\n\n`;
1555
1570
  llmsTxt += `## Pages\n\n`;
1556
1571
  for (const page of indexes) {
1557
- llmsTxt += `- [${page.title}](${baseUrl}${page.url})`;
1572
+ llmsTxt += `- [${page.title}](${baseUrl}${toDocsMarkdownUrl(page.url)})`;
1558
1573
  if (page.description) llmsTxt += `: ${page.description}`;
1559
1574
  llmsTxt += `\n`;
1560
1575
  }
@@ -11,7 +11,7 @@ import path from "node:path";
11
11
  import matter from "gray-matter";
12
12
  import { DocsLayout } from "fumadocs-ui/layouts/docs";
13
13
  import { Suspense } from "react";
14
- import { applySidebarFolderIndexBehavior, buildPageOpenGraph, buildPageTwitter, resolveChangelogConfig, resolveDocsAgentMdxContent, resolveDocsAnalyticsConfig, resolvePageSidebarFolderIndexBehavior, toDocsMarkdownUrl } from "@farming-labs/docs";
14
+ import { applySidebarFolderIndexBehavior, buildPageOpenGraph, buildPageTwitter, renderDocsPageStructuredDataJson, resolveChangelogConfig, resolveDocsAgentMdxContent, resolveDocsAnalyticsConfig, resolveDocsMetadataBaseUrl, resolvePageSidebarFolderIndexBehavior, toDocsMarkdownUrl } from "@farming-labs/docs";
15
15
  import { jsx, jsxs } from "react/jsx-runtime";
16
16
 
17
17
  //#region src/docs-layout.tsx
@@ -380,6 +380,41 @@ function buildReadingTimeMap(config, ctx, options) {
380
380
  scan(docsDir, []);
381
381
  return map;
382
382
  }
383
+ function findDocsPageFile(dir) {
384
+ return ["page.mdx", "page.md"].map((fileName) => path.join(dir, fileName)).find(fs.existsSync);
385
+ }
386
+ function buildStructuredDataMap(config, ctx) {
387
+ const docsDir = ctx.docsDir;
388
+ const map = {};
389
+ const excludedDirs = getExcludedDocsDirs(config, ctx);
390
+ const baseUrl = resolveDocsMetadataBaseUrl(config);
391
+ function scan(dir, slugParts) {
392
+ if (!fs.existsSync(dir)) return;
393
+ if (isExcludedDir(dir, excludedDirs)) return;
394
+ const pagePath = findDocsPageFile(dir);
395
+ if (pagePath) {
396
+ const { data } = matter(fs.readFileSync(pagePath, "utf-8"));
397
+ const route = slugParts.length === 0 ? `/${ctx.entryPath}` : `/${ctx.entryPath}/${slugParts.join("/")}`;
398
+ const title = typeof data.title === "string" ? data.title : slugParts.at(-1)?.replace(/-/g, " ") || "Documentation";
399
+ const description = typeof data.description === "string" ? data.description : void 0;
400
+ const stat = fs.statSync(pagePath);
401
+ map[route] = renderDocsPageStructuredDataJson({
402
+ title,
403
+ description,
404
+ url: withLangInUrl(route, ctx.locale),
405
+ baseUrl,
406
+ entry: ctx.entryPath,
407
+ dateModified: stat.mtime.toISOString()
408
+ });
409
+ }
410
+ for (const name of fs.readdirSync(dir)) {
411
+ const full = path.join(dir, name);
412
+ if (fs.statSync(full).isDirectory()) scan(full, [...slugParts, name]);
413
+ }
414
+ }
415
+ scan(docsDir, []);
416
+ return map;
417
+ }
383
418
  /**
384
419
  * Build a Next.js Metadata object from the docs config.
385
420
  *
@@ -638,6 +673,7 @@ function createDocsLayout(config, options) {
638
673
  enabledByDefault: readingTimeEnabledByDefault,
639
674
  wordsPerMinute: readingTimeWordsPerMinute
640
675
  });
676
+ const structuredDataMap = buildStructuredDataMap(config, localeContext);
641
677
  const readingTimeEnabled = readingTimeEnabledByDefault || Object.keys(readingTimeMap).length > 0;
642
678
  return function DocsLayoutWrapper({ children }) {
643
679
  const tree = applySidebarFolderIndexBehavior(buildTree(config, localeContext, !!sidebarFlat), {
@@ -736,6 +772,7 @@ function createDocsLayout(config, options) {
736
772
  lastUpdatedPosition,
737
773
  readingTimeEnabled,
738
774
  readingTimeMap,
775
+ structuredDataMap,
739
776
  llmsTxtEnabled,
740
777
  descriptionMap,
741
778
  feedbackEnabled: feedbackConfig.enabled,
@@ -43,6 +43,10 @@ interface DocsPageClientProps {
43
43
  readingTimeMap?: Record<string, number>;
44
44
  /** Direct reading-time override for the current page. */
45
45
  readingTime?: number | null;
46
+ /** Map of pathname → serialized Schema.org JSON-LD. */
47
+ structuredDataMap?: Record<string, string>;
48
+ /** Direct serialized Schema.org JSON-LD override for the current page. */
49
+ structuredData?: string;
46
50
  /**
47
51
  * Whether path-based reading time values should render by default.
48
52
  * Explicit `readingTime` overrides can still render when this is false.
@@ -90,6 +94,8 @@ declare function DocsPageClient({
90
94
  lastModified: lastModifiedProp,
91
95
  readingTimeMap,
92
96
  readingTime: readingTimeProp,
97
+ structuredDataMap,
98
+ structuredData: structuredDataProp,
93
99
  readingTimeEnabled,
94
100
  lastUpdatedEnabled,
95
101
  lastUpdatedPosition,
@@ -5,6 +5,7 @@ import { PageActions } from "./page-actions.mjs";
5
5
  import { useWindowSearchParams } from "./client-location.mjs";
6
6
  import { DocsFeedback } from "./docs-feedback.mjs";
7
7
  import { resolveClientLocale, withLangInUrl } from "./i18n.mjs";
8
+ import { escapeJsonLdForScript } from "./json-ld.mjs";
8
9
  import { Children, Fragment, cloneElement, isValidElement, useEffect, useState } from "react";
9
10
  import { DocsBody, DocsPage, EditOnGitHub } from "fumadocs-ui/layouts/docs/page";
10
11
  import { createPortal } from "react-dom";
@@ -163,7 +164,7 @@ function TitleDecorations({ description, belowTitle }) {
163
164
  if (!description && !belowTitle) return null;
164
165
  return /* @__PURE__ */ jsx(Fragment$1, { children: Children.toArray([description, belowTitle].filter(Boolean)) });
165
166
  }
166
- function DocsPageClient({ tocEnabled, tocStyle = "default", breadcrumbEnabled = true, changelogBasePath, entry = "docs", locale, copyMarkdown = false, openDocs = false, openDocsProviders, pageActionsPosition = "below-title", pageActionsAlignment = "left", githubUrl, contentDir, githubBranch = "main", githubDirectory, editOnGithubUrl, lastModifiedMap, lastModified: lastModifiedProp, readingTimeMap, readingTime: readingTimeProp, readingTimeEnabled = false, lastUpdatedEnabled = true, lastUpdatedPosition = "footer", llmsTxtEnabled = false, descriptionMap, description, feedbackEnabled = false, feedbackQuestion, feedbackPlaceholder, feedbackPositiveLabel, feedbackNegativeLabel, feedbackSubmitLabel, feedbackOnFeedback, analytics = false, children }) {
167
+ function DocsPageClient({ tocEnabled, tocStyle = "default", breadcrumbEnabled = true, changelogBasePath, entry = "docs", locale, copyMarkdown = false, openDocs = false, openDocsProviders, pageActionsPosition = "below-title", pageActionsAlignment = "left", githubUrl, contentDir, githubBranch = "main", githubDirectory, editOnGithubUrl, lastModifiedMap, lastModified: lastModifiedProp, readingTimeMap, readingTime: readingTimeProp, structuredDataMap, structuredData: structuredDataProp, readingTimeEnabled = false, lastUpdatedEnabled = true, lastUpdatedPosition = "footer", llmsTxtEnabled = false, descriptionMap, description, feedbackEnabled = false, feedbackQuestion, feedbackPlaceholder, feedbackPositiveLabel, feedbackNegativeLabel, feedbackSubmitLabel, feedbackOnFeedback, analytics = false, children }) {
167
168
  const fdTocStyle = tocStyle === "directional" ? "clerk" : void 0;
168
169
  const [toc, setToc] = useState([]);
169
170
  const [titlePortalHost, setTitlePortalHost] = useState(null);
@@ -175,6 +176,7 @@ function DocsPageClient({ tocEnabled, tocStyle = "default", breadcrumbEnabled =
175
176
  const normalizedPath = (browserPath ?? pathname).replace(/\/$/, "") || "/";
176
177
  const isChangelogRoute = !!(changelogBasePath && (normalizedPath === changelogBasePath || normalizedPath.startsWith(`${changelogBasePath}/`)));
177
178
  const matchedReadingTime = readingTimeMap?.[normalizedPath];
179
+ const structuredDataJson = !isChangelogRoute ? structuredDataProp ?? structuredDataMap?.[normalizedPath] : void 0;
178
180
  useEffect(() => {
179
181
  if (!analytics) return;
180
182
  emitClientAnalyticsEvent({
@@ -341,7 +343,10 @@ function DocsPageClient({ tocEnabled, tocStyle = "default", breadcrumbEnabled =
341
343
  belowTitle: belowTitleBlock
342
344
  }), titlePortalHost) : null;
343
345
  const renderedChildren = Children.toArray(decoratedChildren);
344
- return /* @__PURE__ */ jsxs(DocsPage, {
346
+ return /* @__PURE__ */ jsxs(Fragment$1, { children: [structuredDataJson && /* @__PURE__ */ jsx("script", {
347
+ type: "application/ld+json",
348
+ dangerouslySetInnerHTML: { __html: escapeJsonLdForScript(structuredDataJson) }
349
+ }), /* @__PURE__ */ jsxs(DocsPage, {
345
350
  full: false,
346
351
  toc,
347
352
  tableOfContent: {
@@ -428,7 +433,7 @@ function DocsPageClient({ tocEnabled, tocStyle = "default", breadcrumbEnabled =
428
433
  ]
429
434
  })
430
435
  ]
431
- });
436
+ })] });
432
437
  }
433
438
 
434
439
  //#endregion
@@ -0,0 +1,7 @@
1
+ //#region src/json-ld.ts
2
+ function escapeJsonLdForScript(json) {
3
+ return json.replace(/</g, "\\u003c");
4
+ }
5
+
6
+ //#endregion
7
+ export { escapeJsonLdForScript };
@@ -31,6 +31,7 @@ interface TanstackDocsLayoutProps {
31
31
  description?: string;
32
32
  readingTime?: number | null;
33
33
  lastModified?: string;
34
+ structuredData?: string;
34
35
  editOnGithubUrl?: string;
35
36
  children: ReactNode;
36
37
  }
@@ -41,6 +42,7 @@ declare function TanstackDocsLayout({
41
42
  description,
42
43
  readingTime,
43
44
  lastModified,
45
+ structuredData,
44
46
  editOnGithubUrl,
45
47
  children
46
48
  }: TanstackDocsLayoutProps): react_jsx_runtime0.JSX.Element;
@@ -1,4 +1,5 @@
1
1
  import { withLangInUrl } from "./i18n.mjs";
2
+ import { escapeJsonLdForScript } from "./json-ld.mjs";
2
3
  import { DocsPageClient } from "./docs-page-client.mjs";
3
4
  import { DocsAIFeatures } from "./docs-ai-features.mjs";
4
5
  import { DocsCommandSearch } from "./docs-command-search.mjs";
@@ -8,7 +9,7 @@ import { LocaleThemeControl } from "./locale-theme-control.mjs";
8
9
  import { DocsLayout } from "fumadocs-ui/layouts/docs";
9
10
  import { Suspense } from "react";
10
11
  import { applySidebarFolderIndexBehavior, resolveDocsAnalyticsConfig } from "@farming-labs/docs";
11
- import { jsx, jsxs } from "react/jsx-runtime";
12
+ import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
12
13
 
13
14
  //#region src/tanstack-layout.tsx
14
15
  function resolveTreeIcon(icon, registry) {
@@ -207,7 +208,7 @@ function resolveFeedbackConfig(feedback) {
207
208
  function ForcedThemeScript({ theme }) {
208
209
  return /* @__PURE__ */ jsx("script", { dangerouslySetInnerHTML: { __html: `document.documentElement.classList.remove('light','dark');document.documentElement.classList.add('${theme === "light" || theme === "dark" ? theme : "light"}');` } });
209
210
  }
210
- function TanstackDocsLayout({ config, tree, locale, description, readingTime, lastModified, editOnGithubUrl, children }) {
211
+ function TanstackDocsLayout({ config, tree, locale, description, readingTime, lastModified, structuredData, editOnGithubUrl, children }) {
211
212
  const tocConfig = config.theme?.ui?.layout?.toc;
212
213
  const tocEnabled = tocConfig?.enabled !== false;
213
214
  const tocStyle = tocConfig?.style;
@@ -290,7 +291,7 @@ function TanstackDocsLayout({ config, tree, locale, description, readingTime, la
290
291
  collapsible: sidebarProps.collapsible !== false,
291
292
  flat: !!sidebarFlat
292
293
  });
293
- return /* @__PURE__ */ jsxs(DocsLayout, {
294
+ const layout = /* @__PURE__ */ jsxs(DocsLayout, {
294
295
  tree: resolvedTree,
295
296
  nav: {
296
297
  title: navTitle,
@@ -364,6 +365,11 @@ function TanstackDocsLayout({ config, tree, locale, description, readingTime, la
364
365
  })
365
366
  ]
366
367
  });
368
+ if (!structuredData) return layout;
369
+ return /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx("script", {
370
+ type: "application/ld+json",
371
+ dangerouslySetInnerHTML: { __html: escapeJsonLdForScript(structuredData) }
372
+ }), layout] });
367
373
  }
368
374
 
369
375
  //#endregion
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@farming-labs/theme",
3
- "version": "0.1.84",
3
+ "version": "0.1.86",
4
4
  "description": "Theme package for @farming-labs/docs — layout, provider, MDX components, and styles",
5
5
  "keywords": [
6
6
  "docs",
@@ -139,7 +139,7 @@
139
139
  "tsdown": "^0.20.3",
140
140
  "typescript": "^5.9.3",
141
141
  "vitest": "^3.2.4",
142
- "@farming-labs/docs": "0.1.84"
142
+ "@farming-labs/docs": "0.1.86"
143
143
  },
144
144
  "peerDependencies": {
145
145
  "@farming-labs/docs": ">=0.0.1",