@farming-labs/theme 0.1.73 → 0.1.75

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, DocsI18nConfig, DocsMcpConfig, DocsObservabilityConfig, DocsSearchConfig, FeedbackConfig, OrderingItem } from "@farming-labs/docs";
1
+ import { ChangelogConfig, DocsAnalyticsConfig, DocsAskAIMcpConfig, DocsI18nConfig, DocsMcpConfig, DocsObservabilityConfig, DocsSearchConfig, DocsSitemapConfig, FeedbackConfig, OrderingItem } from "@farming-labs/docs";
2
2
 
3
3
  //#region src/docs-api.d.ts
4
4
  interface AIProviderConfig {
@@ -28,6 +28,7 @@ interface AIOptions {
28
28
  /** Default apiKey when no per-model provider is configured. */
29
29
  apiKey?: string;
30
30
  maxResults?: number;
31
+ useMcp?: boolean | DocsAskAIMcpConfig;
31
32
  }
32
33
  interface DocsAPIOptions {
33
34
  rootDir?: string;
@@ -53,6 +54,8 @@ interface DocsAPIOptions {
53
54
  feedback?: boolean | FeedbackConfig;
54
55
  /** MCP configuration used for the agent discovery spec. */
55
56
  mcp?: boolean | DocsMcpConfig;
57
+ /** Sitemap configuration used for sitemap.xml and sitemap.md. */
58
+ sitemap?: boolean | DocsSitemapConfig;
56
59
  }
57
60
  interface DocsMCPAPIOptions {
58
61
  rootDir?: string;
package/dist/docs-api.mjs CHANGED
@@ -3,8 +3,8 @@ 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, emitDocsAgentTraceEvent, emitDocsAnalyticsEvent, formatDocsAskAIPackageHints, normalizeDocsRelated, performDocsSearch, renderDocsRelatedMarkdownLines, resolveChangelogConfig, resolveDocsI18n, resolveDocsLocale, resolvePageSidebarFolderIndexBehavior, resolveSearchRequestConfig } from "@farming-labs/docs";
7
- import { createDocsMcpHttpHandler, createFilesystemDocsMcpSource, resolveDocsMcpConfig } from "@farming-labs/docs/server";
6
+ import { buildDocsAskAIContext, createDocsAgentTraceContext, createDocsAgentTraceId, createDocsSitemapResponse, emitDocsAgentTraceEvent, emitDocsAnalyticsEvent, formatDocsAskAIPackageHints, normalizeDocsRelated, performDocsSearch, renderDocsRelatedMarkdownLines, resolveAskAISearchRequestConfig, resolveChangelogConfig, resolveDocsI18n, resolveDocsLocale, resolveDocsSitemapConfig, resolvePageSidebarFolderIndexBehavior, resolveSearchRequestConfig } from "@farming-labs/docs";
7
+ import { createDocsMcpHttpHandler, createFilesystemDocsMcpSource, readDocsSitemapManifest, resolveDocsMcpConfig } from "@farming-labs/docs/server";
8
8
 
9
9
  //#region src/docs-api.ts
10
10
  /**
@@ -172,10 +172,11 @@ function isSearchEnabled(search) {
172
172
  if (search && typeof search === "object" && search.enabled === false) return false;
173
173
  return true;
174
174
  }
175
- function buildAgentSpec({ origin, entry, i18n, search, mcp, feedback, llms }) {
175
+ function buildAgentSpec({ origin, entry, i18n, search, mcp, feedback, llms, sitemap }) {
176
176
  const normalizedEntry = normalizePathSegment(entry) || "docs";
177
177
  const localesEnabled = i18n !== null;
178
178
  const searchEnabled = isSearchEnabled(search);
179
+ const sitemapConfig = resolveDocsSitemapConfig(sitemap, { baseUrl: llms.baseUrl });
179
180
  return {
180
181
  version: "1",
181
182
  name: "@farming-labs/docs",
@@ -201,6 +202,7 @@ function buildAgentSpec({ origin, entry, i18n, search, mcp, feedback, llms }) {
201
202
  skills: true,
202
203
  mcp: mcp.enabled,
203
204
  search: searchEnabled,
205
+ sitemap: sitemapConfig.enabled,
204
206
  agentFeedback: feedback.enabled,
205
207
  locales: localesEnabled
206
208
  },
@@ -236,6 +238,20 @@ function buildAgentSpec({ origin, entry, i18n, search, mcp, feedback, llms }) {
236
238
  wellKnownTxt: DEFAULT_LLMS_TXT_WELL_KNOWN_ROUTE,
237
239
  wellKnownFull: DEFAULT_LLMS_FULL_TXT_WELL_KNOWN_ROUTE
238
240
  },
241
+ sitemap: {
242
+ enabled: sitemapConfig.enabled,
243
+ xml: {
244
+ enabled: sitemapConfig.xml.enabled,
245
+ route: sitemapConfig.xml.route,
246
+ api: `${DEFAULT_DOCS_API_ROUTE}?format=sitemap-xml`
247
+ },
248
+ markdown: {
249
+ enabled: sitemapConfig.markdown.enabled,
250
+ route: sitemapConfig.markdown.route,
251
+ wellKnownRoute: sitemapConfig.markdown.wellKnownRoute,
252
+ api: `${DEFAULT_DOCS_API_ROUTE}?format=sitemap-md`
253
+ }
254
+ },
239
255
  search: {
240
256
  enabled: searchEnabled,
241
257
  endpoint: `${DEFAULT_DOCS_API_ROUTE}?query={query}`,
@@ -413,6 +429,7 @@ function readAIConfig(root) {
413
429
  const baseUrlMatch = content.match(/ai\s*:\s*\{[^}]*baseUrl\s*:\s*["']([^"']+)["']/s);
414
430
  const apiKeyMatch = content.match(/ai\s*:\s*\{[^}]*apiKey\s*:\s*process\.env\.(\w+)/s);
415
431
  const maxResultsMatch = content.match(/ai\s*:\s*\{[^}]*maxResults\s*:\s*(\d+)/s);
432
+ const useMcpMatch = content.match(/ai\s*:\s*\{[^}]*useMcp\s*:\s*(true|false)/s);
416
433
  const systemPromptMatch = content.match(/ai\s*:\s*\{[^}]*systemPrompt\s*:\s*["'`]([^"'`]+)["'`]/s);
417
434
  const packageNameMatch = content.match(/ai\s*:\s*\{[^}]*packageName\s*:\s*["'`]([^"'`]+)["'`]/s);
418
435
  const docsUrlMatch = content.match(/ai\s*:\s*\{[^}]*docsUrl\s*:\s*["'`]([^"'`]+)["'`]/s);
@@ -422,6 +439,7 @@ function readAIConfig(root) {
422
439
  baseUrl: baseUrlMatch?.[1],
423
440
  apiKey: apiKeyMatch?.[1] ? process.env[apiKeyMatch[1]] : void 0,
424
441
  maxResults: maxResultsMatch ? parseInt(maxResultsMatch[1], 10) : void 0,
442
+ useMcp: useMcpMatch ? useMcpMatch[1] === "true" : void 0,
425
443
  systemPrompt: systemPromptMatch?.[1],
426
444
  packageName: packageNameMatch?.[1],
427
445
  docsUrl: docsUrlMatch?.[1]
@@ -735,6 +753,8 @@ function scanDocsDir(docsDir, entry, locale, excludedDirs = []) {
735
753
  rawContent,
736
754
  agentFallbackRawContent: agentRawContent !== rawContent ? agentRawContent : void 0,
737
755
  url,
756
+ sourcePath: pageSource.replace(/\\/g, "/"),
757
+ lastModified: fs.statSync(pageSource).mtime.toISOString(),
738
758
  locale
739
759
  });
740
760
  }
@@ -893,11 +913,12 @@ function renderMarkdownDocument(page) {
893
913
  lines.push("", page.agentFallbackRawContent ?? page.rawContent ?? page.content);
894
914
  return lines.join("\n");
895
915
  }
896
- function renderSkillDocument({ origin, entry, search, mcp, feedback, llms }) {
916
+ function renderSkillDocument({ origin, entry, search, mcp, feedback, llms, sitemap }) {
897
917
  const normalizedEntry = normalizePathSegment(entry) || "docs";
898
918
  const siteTitle = compactSkillText(llms.siteTitle ?? "Documentation");
899
919
  const siteDescription = llms.siteDescription ? compactSkillText(llms.siteDescription) : void 0;
900
920
  const searchEnabled = isSearchEnabled(search);
921
+ const sitemapConfig = resolveDocsSitemapConfig(sitemap, { baseUrl: llms.baseUrl });
901
922
  const lines = [
902
923
  "---",
903
924
  "name: docs",
@@ -912,10 +933,18 @@ function renderSkillDocument({ origin, entry, search, mcp, feedback, llms }) {
912
933
  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.");
913
934
  if (searchEnabled) lines.push(`- Search with ${DEFAULT_DOCS_API_ROUTE}?query={query} when you do not know the page.`);
914
935
  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.`);
936
+ if (sitemapConfig.enabled) {
937
+ if (sitemapConfig.xml.enabled) lines.push(`- Use ${sitemapConfig.xml.route} to check canonical page freshness.`);
938
+ if (sitemapConfig.markdown.enabled) lines.push(`- Use ${sitemapConfig.markdown.route} for a semantic docs map.`);
939
+ }
915
940
  if (mcp.enabled) lines.push(`- Use ${DEFAULT_MCP_WELL_KNOWN_ROUTE} or ${DEFAULT_MCP_PUBLIC_ROUTE} for MCP tools when your environment supports MCP.`);
916
941
  if (feedback.enabled) lines.push(`- Read ${feedback.schemaRoute} before posting agent feedback to ${feedback.route}.`);
917
942
  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`);
918
943
  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}`);
944
+ if (sitemapConfig.enabled) {
945
+ if (sitemapConfig.xml.enabled) lines.push(`- Sitemap XML: ${sitemapConfig.xml.route}`);
946
+ if (sitemapConfig.markdown.enabled) lines.push(`- Sitemap Markdown: ${sitemapConfig.markdown.route}`, `- Sitemap well-known alias: ${sitemapConfig.markdown.wellKnownRoute}`);
947
+ }
919
948
  if (mcp.enabled) lines.push(`- MCP: ${DEFAULT_MCP_PUBLIC_ROUTE}, ${DEFAULT_MCP_WELL_KNOWN_ROUTE}`);
920
949
  lines.push("", "## Reusable Framework Skills", "For framework setup, CLI, page actions, Ask AI, or configuration work, install the reusable Farming Labs skills:", "", "```sh", "npx skills add farming-labs/docs", "```");
921
950
  return lines.join("\n");
@@ -1451,6 +1480,31 @@ function readLlmsTxtConfig(root) {
1451
1480
  }
1452
1481
  return { enabled: false };
1453
1482
  }
1483
+ function readSitemapConfig(root) {
1484
+ for (const ext of FILE_EXTS) {
1485
+ const configPath = path.join(root, `docs.config.${ext}`);
1486
+ if (!fs.existsSync(configPath)) continue;
1487
+ try {
1488
+ const content = fs.readFileSync(configPath, "utf-8");
1489
+ if (!content.includes("sitemap")) return void 0;
1490
+ if (/sitemap\s*:\s*false/.test(content)) return false;
1491
+ if (/sitemap\s*:\s*true/.test(content)) return true;
1492
+ const sitemapBlock = content.match(/sitemap\s*:\s*\{([\s\S]*?)\n\s*\}/)?.[1] ?? "";
1493
+ const routePrefix = sitemapBlock.match(/routePrefix\s*:\s*["']([^"']+)["']/)?.[1];
1494
+ const baseUrl = sitemapBlock.match(/baseUrl\s*:\s*["']([^"']+)["']/)?.[1];
1495
+ const manifestPath = sitemapBlock.match(/manifestPath\s*:\s*["']([^"']+)["']/)?.[1];
1496
+ const enabledMatch = sitemapBlock.match(/enabled\s*:\s*(true|false)/);
1497
+ return {
1498
+ enabled: enabledMatch ? enabledMatch[1] === "true" : true,
1499
+ routePrefix,
1500
+ baseUrl,
1501
+ manifestPath
1502
+ };
1503
+ } catch {
1504
+ return;
1505
+ }
1506
+ }
1507
+ }
1454
1508
  function generateLlmsTxt(indexes, options) {
1455
1509
  const { siteTitle = "Documentation", siteDescription, baseUrl = "" } = options;
1456
1510
  let llmsTxt = `# ${siteTitle}\n\n`;
@@ -1511,6 +1565,7 @@ function createDocsAPI(options) {
1511
1565
  const aiConfig = options?.ai ?? readAIConfig(root);
1512
1566
  const searchConfig = options?.search;
1513
1567
  const llmsConfig = readLlmsTxtConfig(root);
1568
+ const sitemapConfig = options?.sitemap ?? readSitemapConfig(root);
1514
1569
  const mcpConfig = resolveDocsMcpConfig(options?.mcp ?? readMcpConfig(root), { defaultName: llmsConfig.siteTitle ?? "Documentation" });
1515
1570
  function resolveDocsDirCandidates(locale) {
1516
1571
  const relativeCandidates = /* @__PURE__ */ new Set();
@@ -1650,7 +1705,8 @@ function createDocsAPI(options) {
1650
1705
  search: searchConfig,
1651
1706
  mcp: mcpConfig,
1652
1707
  feedback: agentFeedbackConfig,
1653
- llms: llmsConfig
1708
+ llms: llmsConfig,
1709
+ sitemap: sitemapConfig
1654
1710
  }), { headers: {
1655
1711
  "Cache-Control": "public, max-age=0, s-maxage=3600",
1656
1712
  "X-Robots-Tag": "noindex"
@@ -1692,13 +1748,24 @@ function createDocsAPI(options) {
1692
1748
  search: searchConfig,
1693
1749
  mcp: mcpConfig,
1694
1750
  feedback: agentFeedbackConfig,
1695
- llms: llmsConfig
1751
+ llms: llmsConfig,
1752
+ sitemap: sitemapConfig
1696
1753
  }), { headers: {
1697
1754
  "Content-Type": "text/markdown; charset=utf-8",
1698
1755
  "Cache-Control": "public, max-age=0, s-maxage=3600",
1699
1756
  "X-Robots-Tag": "noindex"
1700
1757
  } });
1701
1758
  }
1759
+ const sitemapResponse = createDocsSitemapResponse({
1760
+ request,
1761
+ sitemap: sitemapConfig,
1762
+ entry,
1763
+ siteTitle: llmsConfig.siteTitle ?? "Documentation",
1764
+ baseUrl: llmsConfig.baseUrl ?? url.origin,
1765
+ pages: getIndexes(ctx),
1766
+ manifest: readDocsSitemapManifest(root, sitemapConfig)
1767
+ });
1768
+ if (sitemapResponse) return sitemapResponse;
1702
1769
  const markdownRequest = resolveMarkdownRequest(entry, url, request);
1703
1770
  if (markdownRequest) {
1704
1771
  const document = await getMarkdownDocument(ctx, markdownRequest.requestedPath);
@@ -1901,7 +1968,14 @@ function createDocsAPI(options) {
1901
1968
  return Response.json({ error: "AI is not enabled. Set `ai: { enabled: true }` in your docs.config to enable it." }, { status: 404 });
1902
1969
  }
1903
1970
  const ctx = resolveContextFromRequest(request);
1904
- return handleAskAI(request, getIndexes(ctx), aiConfig, resolveSearchRequestConfig(searchConfig, request.url), analytics, observability, { locale: ctx.locale });
1971
+ return handleAskAI(request, getIndexes(ctx), aiConfig, resolveAskAISearchRequestConfig({
1972
+ search: searchConfig,
1973
+ useMcp: aiConfig.useMcp,
1974
+ mcpEndpoint: mcpConfig.route,
1975
+ mcpEnabled: mcpConfig.enabled,
1976
+ mcpSearchEnabled: mcpConfig.tools.searchDocs,
1977
+ requestUrl: request.url
1978
+ }), analytics, observability, { locale: ctx.locale });
1905
1979
  }
1906
1980
  };
1907
1981
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@farming-labs/theme",
3
- "version": "0.1.73",
3
+ "version": "0.1.75",
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.73"
142
+ "@farming-labs/docs": "0.1.75"
143
143
  },
144
144
  "peerDependencies": {
145
145
  "@farming-labs/docs": ">=0.0.1",