@farming-labs/theme 0.1.86 → 0.1.89

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.
@@ -1,4 +1,4 @@
1
- import { ChangelogConfig, DocsAnalyticsConfig, DocsAskAIMcpConfig, DocsI18nConfig, DocsMcpConfig, DocsObservabilityConfig, DocsRobotsConfig, DocsSearchConfig, DocsSitemapConfig, FeedbackConfig, OrderingItem } from "@farming-labs/docs";
1
+ import { ChangelogConfig, DocsAnalyticsConfig, DocsAskAIMcpConfig, DocsI18nConfig, DocsMcpConfig, DocsObservabilityConfig, DocsRobotsConfig, DocsSearchConfig, DocsSitemapConfig, FeedbackConfig, LlmsTxtConfig, OrderingItem } from "@farming-labs/docs";
2
2
 
3
3
  //#region src/docs-api.d.ts
4
4
  interface AIProviderConfig {
@@ -54,6 +54,8 @@ interface DocsAPIOptions {
54
54
  feedback?: boolean | FeedbackConfig;
55
55
  /** MCP configuration used for the agent discovery spec. */
56
56
  mcp?: boolean | DocsMcpConfig;
57
+ /** llms.txt configuration. Enabled by default; set false to opt out. */
58
+ llmsTxt?: boolean | LlmsTxtOptions;
57
59
  /** Sitemap configuration used for sitemap.xml and sitemap.md. */
58
60
  sitemap?: boolean | DocsSitemapConfig;
59
61
  /** Robots.txt generation policy used for the agent discovery spec. */
@@ -72,6 +74,7 @@ interface DocsMCPAPIOptions {
72
74
  analytics?: boolean | DocsAnalyticsConfig;
73
75
  observability?: boolean | DocsObservabilityConfig;
74
76
  }
77
+ type LlmsTxtOptions = LlmsTxtConfig;
75
78
  /**
76
79
  * Create a unified docs API route handler.
77
80
  *
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, toDocsMarkdownUrl } from "@farming-labs/docs";
6
+ import { buildDocsAskAIContext, createDocsAgentTraceContext, createDocsAgentTraceId, createDocsSitemapResponse, emitDocsAgentTraceEvent, emitDocsAnalyticsEvent, formatDocsAskAIPackageHints, getDocsLlmsTxtMaxCharsIssue, getDocsMarkdownVaryHeader, hasDocsMarkdownSignatureAgent, normalizeDocsRelated, performDocsSearch, renderDocsLlmsTxt, renderDocsMarkdownNotFound, renderDocsRelatedMarkdownLines, resolveAskAISearchRequestConfig, resolveChangelogConfig, resolveDocsI18n, resolveDocsLlmsTxtRequest, resolveDocsLlmsTxtSections, resolveDocsLocale, resolveDocsSitemapConfig, resolvePageSidebarFolderIndexBehavior, resolveSearchRequestConfig, selectDocsLlmsTxtContent } 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
@@ -184,6 +184,7 @@ function buildAgentSpec({ origin, entry, i18n, search, mcp, feedback, llms, site
184
184
  const searchEnabled = isSearchEnabled(search);
185
185
  const sitemapConfig = resolveDocsSitemapConfig(sitemap, { baseUrl: llms.baseUrl });
186
186
  const robotsEnabled = isRobotsDiscoveryEnabled(robots);
187
+ const llmsSections = resolveDocsLlmsTxtSections(llms);
187
188
  return {
188
189
  version: "1",
189
190
  name: "@farming-labs/docs",
@@ -245,7 +246,14 @@ function buildAgentSpec({ origin, entry, i18n, search, mcp, feedback, llms, site
245
246
  publicTxt: DEFAULT_LLMS_TXT_ROUTE,
246
247
  publicFull: DEFAULT_LLMS_FULL_TXT_ROUTE,
247
248
  wellKnownTxt: DEFAULT_LLMS_TXT_WELL_KNOWN_ROUTE,
248
- wellKnownFull: DEFAULT_LLMS_FULL_TXT_WELL_KNOWN_ROUTE
249
+ wellKnownFull: DEFAULT_LLMS_FULL_TXT_WELL_KNOWN_ROUTE,
250
+ ...llmsSections.length > 0 ? { sections: llmsSections.map((section) => ({
251
+ title: section.title,
252
+ description: section.description,
253
+ match: section.match,
254
+ txt: section.route,
255
+ full: section.fullRoute
256
+ })) } : {}
249
257
  },
250
258
  sitemap: {
251
259
  enabled: sitemapConfig.enabled,
@@ -927,13 +935,6 @@ function resolveMarkdownRequest(entry, url, request) {
927
935
  }
928
936
  return null;
929
937
  }
930
- function resolveLlmsTxtFormat(url) {
931
- const pathname = normalizeUrlPath(url.pathname);
932
- if (pathname === DEFAULT_LLMS_TXT_ROUTE || pathname === DEFAULT_LLMS_TXT_WELL_KNOWN_ROUTE) return "llms";
933
- if (pathname === DEFAULT_LLMS_FULL_TXT_ROUTE || pathname === DEFAULT_LLMS_FULL_TXT_WELL_KNOWN_ROUTE) return "llms-full";
934
- const format = url.searchParams.get("format");
935
- return format === "llms" || format === "llms-full" ? format : null;
936
- }
937
938
  function renderMarkdownDocument(page) {
938
939
  if ("agentRawContent" in page && page.agentRawContent !== void 0) return page.agentRawContent;
939
940
  const relatedLines = renderDocsRelatedMarkdownLines(page.related);
@@ -950,6 +951,7 @@ function renderSkillDocument({ origin, entry, search, mcp, feedback, llms, sitem
950
951
  const searchEnabled = isSearchEnabled(search);
951
952
  const sitemapConfig = resolveDocsSitemapConfig(sitemap, { baseUrl: llms.baseUrl });
952
953
  const robotsEnabled = isRobotsDiscoveryEnabled(robots);
954
+ const llmsSections = resolveDocsLlmsTxtSections(llms);
953
955
  const lines = [
954
956
  "---",
955
957
  "name: docs",
@@ -963,7 +965,10 @@ function renderSkillDocument({ origin, entry, search, mcp, feedback, llms, sitem
963
965
  if (siteDescription) lines.push(`Description: ${siteDescription}`);
964
966
  lines.push("", "## When To Use", "Use this skill when you need to read or implement against this documentation site.", "", "## Start Here", `- Fetch ${DEFAULT_AGENT_SPEC_WELL_KNOWN_JSON_ROUTE}; fall back to ${DEFAULT_AGENT_SPEC_WELL_KNOWN_ROUTE} or ${DEFAULT_AGENT_SPEC_ROUTE}.`, `- Fetch /${normalizedEntry}.md for the root docs page.`, `- Fetch /${normalizedEntry}/{slug}.md for page-specific context.`, "- You can also request text/markdown from normal page URLs.");
965
967
  if (searchEnabled) lines.push(`- Search with ${DEFAULT_DOCS_API_ROUTE}?query={query} when you do not know the page.`);
966
- if (llms.enabled) lines.push(`- Use ${DEFAULT_LLMS_TXT_ROUTE} for a compact docs index.`, `- Use ${DEFAULT_LLMS_FULL_TXT_ROUTE} for full markdown context.`);
968
+ if (llms.enabled) {
969
+ lines.push(`- Use ${DEFAULT_LLMS_TXT_ROUTE} for a compact docs index.`, `- Use ${DEFAULT_LLMS_FULL_TXT_ROUTE} for full markdown context.`);
970
+ for (const section of llmsSections) lines.push(`- Use ${section.route} for the ${section.title} llms.txt section.`);
971
+ }
967
972
  if (sitemapConfig.enabled) {
968
973
  if (sitemapConfig.xml.enabled) lines.push(`- Use ${sitemapConfig.xml.route} to check canonical page freshness.`);
969
974
  if (sitemapConfig.markdown.enabled) lines.push(`- Use ${sitemapConfig.markdown.route} for a semantic docs map.`);
@@ -973,7 +978,13 @@ function renderSkillDocument({ origin, entry, search, mcp, feedback, llms, sitem
973
978
  if (feedback.enabled) lines.push(`- Read ${feedback.schemaRoute} before posting agent feedback to ${feedback.route}.`);
974
979
  lines.push("", "## Routes", `- Skill document: ${DEFAULT_SKILL_MD_ROUTE}`, `- Skill well-known alias: ${DEFAULT_SKILL_MD_WELL_KNOWN_ROUTE}`, `- Skill API format: ${DEFAULT_DOCS_API_ROUTE}?format=skill`, `- Agent discovery: ${DEFAULT_AGENT_SPEC_WELL_KNOWN_JSON_ROUTE}`, `- Agent discovery fallback: ${DEFAULT_AGENT_SPEC_WELL_KNOWN_ROUTE}`, `- Markdown root: /${normalizedEntry}.md`, `- Markdown pages: /${normalizedEntry}/{slug}.md`);
975
980
  if (robotsEnabled) lines.push(`- Robots policy: ${DEFAULT_ROBOTS_TXT_ROUTE}`);
976
- if (llms.enabled) lines.push(`- llms.txt: ${DEFAULT_LLMS_TXT_ROUTE}`, `- llms-full.txt: ${DEFAULT_LLMS_FULL_TXT_ROUTE}`, `- llms well-known aliases: ${DEFAULT_LLMS_TXT_WELL_KNOWN_ROUTE}, ${DEFAULT_LLMS_FULL_TXT_WELL_KNOWN_ROUTE}`);
981
+ if (llms.enabled) {
982
+ lines.push(`- llms.txt: ${DEFAULT_LLMS_TXT_ROUTE}`, `- llms-full.txt: ${DEFAULT_LLMS_FULL_TXT_ROUTE}`, `- llms well-known aliases: ${DEFAULT_LLMS_TXT_WELL_KNOWN_ROUTE}, ${DEFAULT_LLMS_FULL_TXT_WELL_KNOWN_ROUTE}`);
983
+ for (const section of llmsSections) {
984
+ lines.push(`- ${section.title} llms.txt: ${section.route}`);
985
+ lines.push(`- ${section.title} llms-full.txt: ${section.fullRoute}`);
986
+ }
987
+ }
977
988
  if (sitemapConfig.enabled) {
978
989
  if (sitemapConfig.xml.enabled) lines.push(`- Sitemap XML: ${sitemapConfig.xml.route}`);
979
990
  if (sitemapConfig.markdown.enabled) lines.push(`- Sitemap Markdown: ${sitemapConfig.markdown.route}`, `- Sitemap well-known alias: ${sitemapConfig.markdown.wellKnownRoute}`);
@@ -1495,14 +1506,21 @@ function readLlmsTxtConfig(root) {
1495
1506
  const configPath = path.join(root, `docs.config.${ext}`);
1496
1507
  if (fs.existsSync(configPath)) try {
1497
1508
  const content = fs.readFileSync(configPath, "utf-8");
1498
- if (!content.includes("llmsTxt")) return { enabled: false };
1499
- if (/llmsTxt\s*:\s*true/.test(content)) return { enabled: true };
1509
+ const navTitleMatch = content.match(/nav\s*:\s*\{[^}]*title\s*:\s*["']([^"']+)["']/s);
1510
+ if (!content.includes("llmsTxt")) return {
1511
+ enabled: true,
1512
+ siteTitle: navTitleMatch?.[1]
1513
+ };
1514
+ if (/llmsTxt\s*:\s*true/.test(content)) return {
1515
+ enabled: true,
1516
+ siteTitle: navTitleMatch?.[1]
1517
+ };
1518
+ if (/llmsTxt\s*:\s*false/.test(content)) return { enabled: false };
1500
1519
  const enabledMatch = content.match(/llmsTxt\s*:\s*\{[^}]*enabled\s*:\s*(true|false)/s);
1501
1520
  if (enabledMatch && enabledMatch[1] === "false") return { enabled: false };
1502
1521
  const baseUrlMatch = content.match(/llmsTxt\s*:\s*\{[^}]*baseUrl\s*:\s*["']([^"']+)["']/s);
1503
1522
  const siteTitleMatch = content.match(/llmsTxt\s*:\s*\{[^}]*siteTitle\s*:\s*["']([^"']+)["']/s);
1504
1523
  const siteDescMatch = content.match(/llmsTxt\s*:\s*\{[^}]*siteDescription\s*:\s*["']([^"']+)["']/s);
1505
- const navTitleMatch = content.match(/nav\s*:\s*\{[^}]*title\s*:\s*["']([^"']+)["']/s);
1506
1524
  return {
1507
1525
  enabled: true,
1508
1526
  baseUrl: baseUrlMatch?.[1],
@@ -1511,7 +1529,19 @@ function readLlmsTxtConfig(root) {
1511
1529
  };
1512
1530
  } catch {}
1513
1531
  }
1514
- return { enabled: false };
1532
+ return { enabled: true };
1533
+ }
1534
+ function resolveLlmsTxtConfig(input, fallback) {
1535
+ if (input === void 0) return fallback;
1536
+ if (typeof input === "boolean") return input ? {
1537
+ ...fallback,
1538
+ enabled: true
1539
+ } : { enabled: false };
1540
+ return {
1541
+ ...fallback,
1542
+ ...input,
1543
+ enabled: input.enabled ?? true
1544
+ };
1515
1545
  }
1516
1546
  function readSitemapConfig(root) {
1517
1547
  for (const ext of FILE_EXTS) {
@@ -1564,27 +1594,7 @@ function readRobotsConfig(root) {
1564
1594
  }
1565
1595
  }
1566
1596
  function generateLlmsTxt(indexes, options) {
1567
- const { siteTitle = "Documentation", siteDescription, baseUrl = "" } = options;
1568
- let llmsTxt = `# ${siteTitle}\n\n`;
1569
- if (siteDescription) llmsTxt += `> ${siteDescription}\n\n`;
1570
- llmsTxt += `## Pages\n\n`;
1571
- for (const page of indexes) {
1572
- llmsTxt += `- [${page.title}](${baseUrl}${toDocsMarkdownUrl(page.url)})`;
1573
- if (page.description) llmsTxt += `: ${page.description}`;
1574
- llmsTxt += `\n`;
1575
- }
1576
- let llmsFullTxt = `# ${siteTitle}\n\n`;
1577
- if (siteDescription) llmsFullTxt += `> ${siteDescription}\n\n`;
1578
- for (const page of indexes) {
1579
- llmsFullTxt += `## ${page.title}\n\n`;
1580
- llmsFullTxt += `URL: ${baseUrl}${page.url}\n\n`;
1581
- if (page.description) llmsFullTxt += `${page.description}\n\n`;
1582
- llmsFullTxt += `${page.content}\n\n---\n\n`;
1583
- }
1584
- return {
1585
- llmsTxt,
1586
- llmsFullTxt
1587
- };
1597
+ return renderDocsLlmsTxt(indexes, options);
1588
1598
  }
1589
1599
  /**
1590
1600
  * Create a unified docs API route handler.
@@ -1622,7 +1632,7 @@ function createDocsAPI(options) {
1622
1632
  const i18n = resolveDocsI18n(options?.i18n ?? readI18nConfig(root));
1623
1633
  const aiConfig = options?.ai ?? readAIConfig(root);
1624
1634
  const searchConfig = options?.search;
1625
- const llmsConfig = readLlmsTxtConfig(root);
1635
+ const llmsConfig = resolveLlmsTxtConfig(options?.llmsTxt, readLlmsTxtConfig(root));
1626
1636
  const sitemapConfig = options?.sitemap ?? readSitemapConfig(root);
1627
1637
  const robotsConfig = options?.robots ?? readRobotsConfig(root);
1628
1638
  const mcpConfig = resolveDocsMcpConfig(options?.mcp ?? readMcpConfig(root), { defaultName: llmsConfig.siteTitle ?? "Documentation" });
@@ -1739,7 +1749,9 @@ function createDocsAPI(options) {
1739
1749
  const next = generateLlmsTxt(getIndexes(ctx), {
1740
1750
  siteTitle: llmsConfig.siteTitle ?? "Documentation",
1741
1751
  siteDescription: llmsConfig.siteDescription,
1742
- baseUrl: llmsConfig.baseUrl ?? ""
1752
+ baseUrl: llmsConfig.baseUrl ?? "",
1753
+ maxChars: llmsConfig.maxChars,
1754
+ sections: llmsConfig.sections
1743
1755
  });
1744
1756
  llmsCacheByLocale.set(key, next);
1745
1757
  return next;
@@ -1902,31 +1914,45 @@ function createDocsAPI(options) {
1902
1914
  "X-Robots-Tag": "noindex"
1903
1915
  } });
1904
1916
  }
1905
- const llmsFormat = resolveLlmsTxtFormat(url);
1906
- if (llmsFormat === "llms") {
1907
- await emitDocsAnalyticsEvent(analytics, {
1908
- type: "llms_request",
1909
- source: "server",
1910
- url: request.url,
1911
- path: url.pathname,
1912
- locale: ctx.locale,
1913
- properties: { format: "llms" }
1917
+ const llmsRequest = resolveDocsLlmsTxtRequest(url, llmsConfig);
1918
+ if (llmsRequest) {
1919
+ if (!llmsConfig.enabled) return new Response("Not Found", {
1920
+ status: 404,
1921
+ headers: {
1922
+ "Content-Type": "text/plain; charset=utf-8",
1923
+ "X-Robots-Tag": "noindex"
1924
+ }
1914
1925
  });
1915
- return new Response(getLlmsContent(ctx).llmsTxt, { headers: {
1916
- "Content-Type": "text/plain; charset=utf-8",
1917
- "Cache-Control": "public, max-age=3600"
1918
- } });
1919
- }
1920
- if (llmsFormat === "llms-full") {
1926
+ const selected = selectDocsLlmsTxtContent(getLlmsContent(ctx), llmsRequest);
1927
+ if (!selected) return new Response("Not Found", {
1928
+ status: 404,
1929
+ headers: {
1930
+ "Content-Type": "text/plain; charset=utf-8",
1931
+ "X-Robots-Tag": "noindex"
1932
+ }
1933
+ });
1934
+ const budgetIssue = getDocsLlmsTxtMaxCharsIssue(selected.label, selected.content, selected.maxChars);
1935
+ if (budgetIssue?.mode === "error") return new Response(budgetIssue.message, {
1936
+ status: 500,
1937
+ headers: {
1938
+ "Content-Type": "text/plain; charset=utf-8",
1939
+ "X-Robots-Tag": "noindex"
1940
+ }
1941
+ });
1942
+ if (budgetIssue?.mode === "warn") console.warn(`[docs] ${budgetIssue.message}`);
1921
1943
  await emitDocsAnalyticsEvent(analytics, {
1922
1944
  type: "llms_request",
1923
1945
  source: "server",
1924
1946
  url: request.url,
1925
1947
  path: url.pathname,
1926
1948
  locale: ctx.locale,
1927
- properties: { format: "llms-full" }
1949
+ properties: {
1950
+ format: llmsRequest.format,
1951
+ section: llmsRequest.section?.route,
1952
+ contentLength: selected.content.length
1953
+ }
1928
1954
  });
1929
- return new Response(getLlmsContent(ctx).llmsFullTxt, { headers: {
1955
+ return new Response(selected.content, { headers: {
1930
1956
  "Content-Type": "text/plain; charset=utf-8",
1931
1957
  "Cache-Control": "public, max-age=3600"
1932
1958
  } });
@@ -636,7 +636,7 @@ function createDocsLayout(config, options) {
636
636
  const readingTimeOptions = resolveReadingTimeOptions(config.readingTime);
637
637
  const readingTimeEnabledByDefault = readingTimeOptions.enabled;
638
638
  const readingTimeWordsPerMinute = readingTimeOptions.wordsPerMinute ?? 220;
639
- const llmsTxtEnabled = resolveBool(config.llmsTxt);
639
+ const llmsTxtEnabled = resolveEnabledByDefault(config.llmsTxt);
640
640
  const feedbackConfig = resolveFeedbackConfig(config.feedback);
641
641
  const openDocsProviders = (pageActions?.openDocs && typeof pageActions.openDocs === "object" && pageActions.openDocs.providers ? pageActions.openDocs.providers : void 0)?.map((p) => ({
642
642
  name: p.name,
@@ -795,6 +795,11 @@ function resolveBool(v) {
795
795
  if (typeof v === "boolean") return v;
796
796
  return v.enabled !== false;
797
797
  }
798
+ function resolveEnabledByDefault(v) {
799
+ if (v === void 0) return true;
800
+ if (typeof v === "boolean") return v;
801
+ return v.enabled !== false;
802
+ }
798
803
  function resolveFeedbackConfig(feedback) {
799
804
  const defaults = {
800
805
  enabled: false,
@@ -181,6 +181,11 @@ function resolveBool(value) {
181
181
  if (typeof value === "boolean") return value;
182
182
  return value.enabled !== false;
183
183
  }
184
+ function resolveEnabledByDefault(value) {
185
+ if (value === void 0) return true;
186
+ if (typeof value === "boolean") return value;
187
+ return value.enabled !== false;
188
+ }
184
189
  function resolveFeedbackConfig(feedback) {
185
190
  const defaults = {
186
191
  enabled: false,
@@ -237,7 +242,7 @@ function TanstackDocsLayout({ config, tree, locale, description, readingTime, la
237
242
  const lastUpdatedEnabled = lastUpdatedRaw !== false && (typeof lastUpdatedRaw !== "object" || lastUpdatedRaw.enabled !== false);
238
243
  const lastUpdatedPosition = typeof lastUpdatedRaw === "object" ? lastUpdatedRaw.position ?? "footer" : "footer";
239
244
  const readingTimeEnabled = resolveReadingTimeOptions(config.readingTime).enabled;
240
- const llmsTxtEnabled = resolveBool(config.llmsTxt);
245
+ const llmsTxtEnabled = resolveEnabledByDefault(config.llmsTxt);
241
246
  const feedbackConfig = resolveFeedbackConfig(config.feedback);
242
247
  const staticExport = !!config.staticExport;
243
248
  const openDocsProviders = (pageActions?.openDocs && typeof pageActions.openDocs === "object" && pageActions.openDocs.providers ? pageActions.openDocs.providers : void 0)?.map((provider) => ({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@farming-labs/theme",
3
- "version": "0.1.86",
3
+ "version": "0.1.89",
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.86"
142
+ "@farming-labs/docs": "0.1.89"
143
143
  },
144
144
  "peerDependencies": {
145
145
  "@farming-labs/docs": ">=0.0.1",