@farming-labs/theme 0.1.99 → 0.1.100

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, LlmsTxtConfig, OrderingItem } from "@farming-labs/docs";
1
+ import { ChangelogConfig, DocsAnalyticsConfig, DocsAskAIMcpConfig, DocsConfig, 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 {
@@ -60,6 +60,10 @@ interface DocsAPIOptions {
60
60
  sitemap?: boolean | DocsSitemapConfig;
61
61
  /** Robots.txt generation policy used for the agent discovery spec. */
62
62
  robots?: boolean | DocsRobotsConfig;
63
+ /** API reference / OpenAPI schema configuration used for agent discovery. */
64
+ apiReference?: DocsConfig["apiReference"];
65
+ /** Metadata used in generated OpenAPI documents. */
66
+ metadata?: DocsConfig["metadata"];
63
67
  }
64
68
  interface DocsMCPAPIOptions {
65
69
  rootDir?: string;
package/dist/docs-api.mjs CHANGED
@@ -4,7 +4,7 @@ import fs from "node:fs";
4
4
  import path from "node:path";
5
5
  import matter from "gray-matter";
6
6
  import { buildDocsAskAIContext, createDocsAgentTraceContext, createDocsAgentTraceId, createDocsRobotsResponse, createDocsSitemapResponse, emitDocsAgentTraceEvent, emitDocsAnalyticsEvent, formatDocsAskAIPackageHints, getDocsLlmsTxtMaxCharsIssue, getDocsMarkdownCanonicalLinkHeader, getDocsMarkdownVaryHeader, hasDocsMarkdownSignatureAgent, normalizeDocsRelated, performDocsSearch, renderDocsLlmsTxt, renderDocsMarkdownNotFound, renderDocsRelatedMarkdownLines, resolveAskAISearchRequestConfig, resolveChangelogConfig, resolveDocsI18n, resolveDocsLlmsTxtRequest, resolveDocsLlmsTxtSections, resolveDocsLocale, resolveDocsSitemapConfig, resolvePageSidebarFolderIndexBehavior, resolveSearchRequestConfig, selectDocsLlmsTxtContent } from "@farming-labs/docs";
7
- import { createDocsMcpHttpHandler, createFilesystemDocsMcpSource, readDocsSitemapManifest, resolveDocsMcpConfig } from "@farming-labs/docs/server";
7
+ import { buildApiReferenceOpenApiDocumentAsync, createDocsMcpHttpHandler, createFilesystemDocsMcpSource, readDocsSitemapManifest, resolveApiReferenceConfig, resolveDocsMcpConfig } from "@farming-labs/docs/server";
8
8
 
9
9
  //#region src/docs-api.ts
10
10
  /**
@@ -181,7 +181,21 @@ function isRobotsDiscoveryEnabled(robots) {
181
181
  if (robots && typeof robots === "object" && robots.enabled === false) return false;
182
182
  return true;
183
183
  }
184
- function buildAgentSpec({ origin, entry, i18n, search, mcp, feedback, llms, sitemap, robots }) {
184
+ function isApiReferenceOpenApiRequest(url) {
185
+ return url.searchParams.get("format")?.trim() === "openapi";
186
+ }
187
+ function resolveApiReferenceOpenApiDiscovery(value) {
188
+ const apiReference = resolveApiReferenceConfig(value);
189
+ if (!apiReference.enabled) return { enabled: false };
190
+ return {
191
+ enabled: true,
192
+ url: `${DEFAULT_DOCS_API_ROUTE}?format=openapi`,
193
+ source: apiReference.specUrl ? "configured" : "generated",
194
+ specUrl: apiReference.specUrl,
195
+ apiReferencePath: `/${apiReference.path}`
196
+ };
197
+ }
198
+ function buildAgentSpec({ origin, entry, i18n, search, mcp, feedback, llms, sitemap, robots, openapi }) {
185
199
  const normalizedEntry = normalizePathSegment(entry) || "docs";
186
200
  const localesEnabled = i18n !== null;
187
201
  const searchEnabled = isSearchEnabled(search);
@@ -216,6 +230,8 @@ function buildAgentSpec({ origin, entry, i18n, search, mcp, feedback, llms, site
216
230
  sitemap: sitemapConfig.enabled,
217
231
  robots: robotsEnabled,
218
232
  structuredData: true,
233
+ apiReference: openapi.enabled,
234
+ openapi: openapi.enabled,
219
235
  agentFeedback: feedback.enabled,
220
236
  locales: localesEnabled
221
237
  },
@@ -226,7 +242,8 @@ function buildAgentSpec({ origin, entry, i18n, search, mcp, feedback, llms, site
226
242
  agentSpecFallback: DEFAULT_AGENT_SPEC_WELL_KNOWN_ROUTE,
227
243
  agentSpecWellKnown: DEFAULT_AGENT_SPEC_WELL_KNOWN_ROUTE,
228
244
  agentSpecWellKnownJson: DEFAULT_AGENT_SPEC_WELL_KNOWN_JSON_ROUTE,
229
- agentSpecQuery: `${DEFAULT_DOCS_API_ROUTE}?agent=spec`
245
+ agentSpecQuery: `${DEFAULT_DOCS_API_ROUTE}?agent=spec`,
246
+ openapi: `${DEFAULT_DOCS_API_ROUTE}?format=openapi`
230
247
  },
231
248
  markdown: {
232
249
  enabled: true,
@@ -291,6 +308,14 @@ function buildAgentSpec({ origin, entry, i18n, search, mcp, feedback, llms, site
291
308
  canonicalUrlField: "url",
292
309
  breadcrumbType: "BreadcrumbList"
293
310
  },
311
+ openapi: {
312
+ enabled: openapi.enabled,
313
+ url: openapi.url ?? null,
314
+ source: openapi.source ?? null,
315
+ specUrl: openapi.specUrl ?? null,
316
+ apiReferencePath: openapi.apiReferencePath ?? null,
317
+ format: "OpenAPI 3.1"
318
+ },
294
319
  search: {
295
320
  enabled: searchEnabled,
296
321
  endpoint: `${DEFAULT_DOCS_API_ROUTE}?query={query}`,
@@ -334,6 +359,7 @@ function buildAgentSpec({ origin, entry, i18n, search, mcp, feedback, llms, site
334
359
  instructions: {
335
360
  preferMarkdownRoutes: true,
336
361
  useMcpWhenAvailable: true,
362
+ useOpenApiWhenAvailable: true,
337
363
  readFeedbackSchemaBeforeSubmitting: true,
338
364
  doNotAssumeFeedbackPayloadShape: true
339
365
  }
@@ -948,7 +974,7 @@ function renderMarkdownDocument(page, options = {}) {
948
974
  lines.push("", page.agentFallbackRawContent ?? page.rawContent ?? page.content);
949
975
  return lines.join("\n");
950
976
  }
951
- function renderSkillDocument({ origin, entry, search, mcp, feedback, llms, sitemap, robots }) {
977
+ function renderSkillDocument({ origin, entry, search, mcp, feedback, llms, sitemap, robots, openapi }) {
952
978
  const normalizedEntry = normalizePathSegment(entry) || "docs";
953
979
  const siteTitle = compactSkillText(llms.siteTitle ?? "Documentation");
954
980
  const siteDescription = llms.siteDescription ? compactSkillText(llms.siteDescription) : void 0;
@@ -969,6 +995,7 @@ function renderSkillDocument({ origin, entry, search, mcp, feedback, llms, sitem
969
995
  if (siteDescription) lines.push(`Description: ${siteDescription}`);
970
996
  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.");
971
997
  if (searchEnabled) lines.push(`- Search with ${DEFAULT_DOCS_API_ROUTE}?query={query} when you do not know the page.`);
998
+ if (openapi.enabled && openapi.url) lines.push(`- Fetch ${openapi.url} for the machine-readable OpenAPI schema before scraping API reference pages.`);
972
999
  if (llms.enabled) {
973
1000
  lines.push(`- Use ${DEFAULT_LLMS_TXT_ROUTE} for a compact docs index.`, `- Use ${DEFAULT_LLMS_FULL_TXT_ROUTE} for full markdown context.`);
974
1001
  for (const section of llmsSections) lines.push(`- Use ${section.route} for the ${section.title} llms.txt section.`);
@@ -989,6 +1016,10 @@ function renderSkillDocument({ origin, entry, search, mcp, feedback, llms, sitem
989
1016
  lines.push(`- ${section.title} llms-full.txt: ${section.fullRoute}`);
990
1017
  }
991
1018
  }
1019
+ if (openapi.enabled && openapi.url) {
1020
+ lines.push(`- OpenAPI schema: ${openapi.url}`);
1021
+ if (openapi.apiReferencePath) lines.push(`- API reference: ${openapi.apiReferencePath}`);
1022
+ }
992
1023
  if (sitemapConfig.enabled) {
993
1024
  if (sitemapConfig.xml.enabled) lines.push(`- Sitemap XML: ${sitemapConfig.xml.route}`);
994
1025
  if (sitemapConfig.markdown.enabled) lines.push(`- Sitemap Markdown: ${sitemapConfig.markdown.route}`, `- Sitemap well-known alias: ${sitemapConfig.markdown.wellKnownRoute}`);
@@ -1597,6 +1628,37 @@ function readRobotsConfig(root) {
1597
1628
  }
1598
1629
  }
1599
1630
  }
1631
+ function readApiReferenceConfig(root) {
1632
+ for (const ext of FILE_EXTS) {
1633
+ const configPath = path.join(root, `docs.config.${ext}`);
1634
+ if (!fs.existsSync(configPath)) continue;
1635
+ try {
1636
+ const content = fs.readFileSync(configPath, "utf-8");
1637
+ if (!content.includes("apiReference")) return void 0;
1638
+ if (/apiReference\s*:\s*false/.test(content)) return false;
1639
+ if (/apiReference\s*:\s*true/.test(content)) return true;
1640
+ const block = content.match(/apiReference\s*:\s*\{([\s\S]*?)\n\s*\}/)?.[1] ?? "";
1641
+ const enabledMatch = block.match(/enabled\s*:\s*(true|false)/);
1642
+ const pathMatch = block.match(/path\s*:\s*["']([^"']+)["']/);
1643
+ const routeRootMatch = block.match(/routeRoot\s*:\s*["']([^"']+)["']/);
1644
+ const specUrlMatch = block.match(/specUrl\s*:\s*["']([^"']+)["']/);
1645
+ const rendererMatch = block.match(/renderer\s*:\s*["'](fumadocs|scalar)["']/);
1646
+ const excludeMatch = block.match(/exclude\s*:\s*\[([\s\S]*?)\]/);
1647
+ const exclude = excludeMatch?.[1] === void 0 ? void 0 : Array.from(excludeMatch[1].matchAll(/["']([^"']+)["']/g)).map((match) => match[1]).filter(Boolean);
1648
+ const renderer = rendererMatch?.[1] === "fumadocs" || rendererMatch?.[1] === "scalar" ? rendererMatch[1] : void 0;
1649
+ return {
1650
+ enabled: enabledMatch ? enabledMatch[1] === "true" : true,
1651
+ path: pathMatch?.[1],
1652
+ routeRoot: routeRootMatch?.[1],
1653
+ specUrl: specUrlMatch?.[1],
1654
+ renderer,
1655
+ exclude
1656
+ };
1657
+ } catch {
1658
+ return;
1659
+ }
1660
+ }
1661
+ }
1600
1662
  function generateLlmsTxt(indexes, options) {
1601
1663
  return renderDocsLlmsTxt(indexes, options);
1602
1664
  }
@@ -1639,6 +1701,13 @@ function createDocsAPI(options) {
1639
1701
  const llmsConfig = resolveLlmsTxtConfig(options?.llmsTxt, readLlmsTxtConfig(root));
1640
1702
  const sitemapConfig = options?.sitemap ?? readSitemapConfig(root);
1641
1703
  const robotsConfig = options?.robots ?? readRobotsConfig(root);
1704
+ const apiReferenceConfig = options?.apiReference ?? readApiReferenceConfig(root);
1705
+ const apiReferenceDocsConfig = {
1706
+ ...options,
1707
+ entry,
1708
+ apiReference: apiReferenceConfig
1709
+ };
1710
+ const openapiDiscovery = resolveApiReferenceOpenApiDiscovery(apiReferenceConfig);
1642
1711
  const mcpConfig = resolveDocsMcpConfig(options?.mcp ?? readMcpConfig(root), { defaultName: llmsConfig.siteTitle ?? "Documentation" });
1643
1712
  function resolveDocsDirCandidates(locale) {
1644
1713
  const relativeCandidates = /* @__PURE__ */ new Set();
@@ -1755,7 +1824,8 @@ function createDocsAPI(options) {
1755
1824
  siteDescription: llmsConfig.siteDescription,
1756
1825
  baseUrl: llmsConfig.baseUrl ?? "",
1757
1826
  maxChars: llmsConfig.maxChars,
1758
- sections: llmsConfig.sections
1827
+ sections: llmsConfig.sections,
1828
+ openapi: openapiDiscovery
1759
1829
  });
1760
1830
  llmsCacheByLocale.set(key, next);
1761
1831
  return next;
@@ -1782,12 +1852,32 @@ function createDocsAPI(options) {
1782
1852
  feedback: agentFeedbackConfig,
1783
1853
  llms: llmsConfig,
1784
1854
  sitemap: sitemapConfig,
1785
- robots: robotsConfig
1855
+ robots: robotsConfig,
1856
+ openapi: openapiDiscovery
1786
1857
  }), { headers: {
1787
1858
  "Cache-Control": "public, max-age=0, s-maxage=3600",
1788
1859
  "X-Robots-Tag": "noindex"
1789
1860
  } });
1790
1861
  }
1862
+ if (isApiReferenceOpenApiRequest(url)) {
1863
+ if (!openapiDiscovery.enabled) return new Response("Not Found", {
1864
+ status: 404,
1865
+ headers: {
1866
+ "Content-Type": "text/plain; charset=utf-8",
1867
+ "X-Robots-Tag": "noindex"
1868
+ }
1869
+ });
1870
+ const document = await buildApiReferenceOpenApiDocumentAsync(apiReferenceDocsConfig, {
1871
+ framework: "next",
1872
+ rootDir: root,
1873
+ baseUrl: url.origin
1874
+ });
1875
+ return new Response(JSON.stringify(document, null, 2), { headers: {
1876
+ "Content-Type": "application/json; charset=utf-8",
1877
+ "Cache-Control": "public, max-age=0, s-maxage=3600",
1878
+ "X-Robots-Tag": "noindex"
1879
+ } });
1880
+ }
1791
1881
  const agentFeedbackRequest = resolveAgentFeedbackRequest(url, agentFeedbackConfig);
1792
1882
  if (agentFeedbackRequest) {
1793
1883
  if (agentFeedbackRequest.kind === "submit") return Response.json({ error: "Method Not Allowed" }, {
@@ -1826,7 +1916,8 @@ function createDocsAPI(options) {
1826
1916
  feedback: agentFeedbackConfig,
1827
1917
  llms: llmsConfig,
1828
1918
  sitemap: sitemapConfig,
1829
- robots: robotsConfig
1919
+ robots: robotsConfig,
1920
+ openapi: openapiDiscovery
1830
1921
  }), { headers: {
1831
1922
  "Content-Type": "text/markdown; charset=utf-8",
1832
1923
  "Cache-Control": "public, max-age=0, s-maxage=3600",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@farming-labs/theme",
3
- "version": "0.1.99",
3
+ "version": "0.1.100",
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.99"
142
+ "@farming-labs/docs": "0.1.100"
143
143
  },
144
144
  "peerDependencies": {
145
145
  "@farming-labs/docs": ">=0.0.1",