{"version":3,"sources":["../src/app-markdown.ts","../src/markdown.ts","../src/app-markdown-config.ts"],"sourcesContent":["import { readFile } from \"fs/promises\";\nimport path from \"path\";\nimport { pathToFileURL } from \"url\";\nimport type { Metadata, RouteModule } from \"./types\";\nimport type { ComponentType } from \"react\";\nimport { requestAcceptsMarkdown } from \"./markdown\";\nimport {\n  resolveMdxConfig,\n  type FarmMdxComponents,\n  type FarmMdxResolvedConfig,\n} from \"./app-markdown-config\";\nexport {\n  resolveMdxConfig,\n  type FarmMdxComponent,\n  type FarmMdxComponents,\n  type FarmMdxResolvedConfig,\n  type FarmMdxUserConfig,\n} from \"./app-markdown-config\";\n\nexport interface FarmMarkdownPageSource {\n  source: string;\n  filePath: string;\n}\n\nexport interface FarmMarkdownPageModuleInput extends FarmMarkdownPageSource {\n  components?: FarmMdxComponents;\n  config?: FarmMdxResolvedConfig;\n}\n\nexport function isFarmMarkdownPageFile(filePath: string): boolean {\n  return /(^|[/\\\\])page\\.mdx?$/i.test(filePath);\n}\n\n/** Content type for Markdown responses, including agent-facing error bodies. */\nexport const FARM_MARKDOWN_CONTENT_TYPE = \"text/markdown; charset=utf-8\";\n\n/**\n * True when the client asked for Markdown — either via a `.md` path or an\n * `Accept: text/markdown` header. Used to serve a Markdown error body to agents\n * that navigate in Markdown, instead of the HTML error shell.\n */\nexport function farmRequestWantsMarkdown(\n  pathname: string,\n  accept: string | null | undefined,\n): boolean {\n  return pathname.toLowerCase().endsWith(\".md\") || requestAcceptsMarkdown(accept);\n}\n\n/**\n * A Markdown error document for agents that requested Markdown but hit an error\n * (typically a 404). Includes a short explanation and a link back to the site so\n * an agent can recover, satisfying the \"Markdown error body\" agent-readiness\n * expectation.\n */\nexport function createFarmMarkdownErrorBody(\n  status: number,\n  pathname: string,\n  homeHref = \"/\",\n): string {\n  const heading = status === 404 ? \"Page not found\" : `Request failed (${status})`;\n  return (\n    `# ${heading}\\n\\n` +\n    `No page is available at \\`${pathname}\\`. The URL may be incorrect, ` +\n    `or the page has not been published.\\n\\n` +\n    `Browse [the site homepage](${homeHref}) to find available pages.\\n`\n  );\n}\n\nexport function normalizeFarmMarkdownRoutePath(pathname: string): string {\n  const withoutExtension = pathname.replace(/\\.md$/i, \"\");\n  const normalized = withoutExtension.startsWith(\"/\") ? withoutExtension : `/${withoutExtension}`;\n  return normalized === \"/index\" ? \"/\" : normalized.replace(/\\/+$/g, \"\") || \"/\";\n}\n\nexport function parseMarkdownFrontmatter(source: string): {\n  frontmatter: Record<string, string>;\n  body: string;\n} {\n  if (!source.startsWith(\"---\")) {\n    return { frontmatter: {}, body: source };\n  }\n\n  const endIndex = source.indexOf(\"\\n---\", 3);\n  if (endIndex === -1) {\n    return { frontmatter: {}, body: source };\n  }\n\n  const frontmatterSource = source.slice(3, endIndex).trim();\n  const closingFenceEnd = endIndex + \"\\n---\".length;\n  const bodyStart =\n    source.slice(closingFenceEnd, closingFenceEnd + 2) === \"\\r\\n\"\n      ? closingFenceEnd + 2\n      : source[closingFenceEnd] === \"\\n\"\n        ? closingFenceEnd + 1\n        : closingFenceEnd;\n  const body = source.slice(bodyStart);\n  const frontmatter: Record<string, string> = {};\n\n  for (const line of frontmatterSource.split(/\\r?\\n/)) {\n    const separator = line.indexOf(\":\");\n    if (separator === -1) continue;\n    const key = line.slice(0, separator).trim();\n    const value = line\n      .slice(separator + 1)\n      .trim()\n      .replace(/^[\"']|[\"']$/g, \"\");\n    if (key && value) frontmatter[key] = value;\n  }\n\n  return { frontmatter, body };\n}\n\nexport function titleFromMarkdown(body: string, fallback?: string): string | undefined {\n  return body.match(/^#\\s+(.+)$/m)?.[1]?.trim() || fallback;\n}\n\nexport function createMarkdownMetadata(\n  source: string,\n  filePath: string,\n): (Metadata & Record<string, any>) | undefined {\n  const { frontmatter, body } = parseMarkdownFrontmatter(source);\n  const routeTitle = titleFromMarkdown(body);\n  const title = frontmatter.title || routeTitle;\n  const description = frontmatter.description;\n\n  if (!title && !description) {\n    return undefined;\n  }\n\n  return {\n    ...(title ? { title } : {}),\n    ...(description ? { description } : {}),\n    source: filePath,\n  };\n}\n\nasync function evaluateMdxSource(source: string, filePath: string) {\n  const [{ evaluate }, runtime, remarkGfmModule] = await Promise.all([\n    import(\"@mdx-js/mdx\"),\n    import(\"react/jsx-runtime\"),\n    import(\"remark-gfm\"),\n  ]);\n  const remarkGfm = remarkGfmModule.default;\n\n  return evaluate(source, {\n    ...runtime,\n    baseUrl: pathToFileURL(filePath),\n    remarkPlugins: [remarkGfm],\n  });\n}\n\nexport function createFarmMarkdownRouteModule(\n  input: FarmMarkdownPageModuleInput,\n): RouteModule & { source: string } {\n  const { body } = parseMarkdownFrontmatter(input.source);\n  const components = input.components || {};\n  const className = input.config?.className || \"farm-markdown\";\n  let evaluatedPromise: Promise<{ default: ComponentType<any> }> | undefined;\n\n  const loadContent = async () => {\n    evaluatedPromise ??= evaluateMdxSource(body, input.filePath) as Promise<{\n      default: ComponentType<any>;\n    }>;\n    return evaluatedPromise;\n  };\n\n  async function FarmMarkdownPage(props: any) {\n    const { createElement } = await import(\"react\");\n    const evaluated = await loadContent();\n    const MDXContent = evaluated.default;\n    return createElement(\n      \"article\",\n      { className, \"data-farm-markdown-page\": \"\" },\n      createElement(MDXContent, {\n        ...props,\n        components: {\n          ...components,\n          ...props?.components,\n        },\n      }),\n    );\n  }\n\n  return {\n    default: FarmMarkdownPage as unknown as RouteModule[\"default\"],\n    metadata: createMarkdownMetadata(input.source, input.filePath),\n    source: input.source,\n  };\n}\n\nexport async function createFarmMarkdownRouteModuleFromFile(\n  filePath: string,\n  options: {\n    components?: FarmMdxComponents;\n    config?: FarmMdxResolvedConfig;\n  } = {},\n) {\n  const source = await readFile(filePath, \"utf8\");\n  return createFarmMarkdownRouteModule({\n    source,\n    filePath,\n    components: options.components,\n    config: options.config,\n  });\n}\n\nexport async function loadFarmMdxComponents(\n  config: FarmMdxResolvedConfig | undefined,\n  options: {\n    root: string;\n    loadModule?: (modulePath: string) => Promise<unknown>;\n  },\n): Promise<FarmMdxComponents> {\n  const configured = config?.components;\n  if (!configured) {\n    return {};\n  }\n\n  if (typeof configured !== \"string\") {\n    return configured;\n  }\n\n  const modulePath = path.isAbsolute(configured) ? configured : path.join(options.root, configured);\n  const mod = options.loadModule\n    ? await options.loadModule(modulePath)\n    : await import(pathToFileURL(modulePath).href);\n  const maybeModule = mod as {\n    components?: FarmMdxComponents;\n    default?: FarmMdxComponents;\n  };\n\n  return maybeModule.components || maybeModule.default || {};\n}\n\nexport async function createFarmMarkdownSourceResponse(options: {\n  request: Request;\n  config?: FarmMdxResolvedConfig;\n  resolveSource: (\n    pathname: string,\n  ) => Promise<FarmMarkdownPageSource | null> | FarmMarkdownPageSource | null;\n}): Promise<Response | null> {\n  if (options.request.method !== \"GET\" && options.request.method !== \"HEAD\") {\n    return null;\n  }\n  if (options.config?.markdownRoutes === false) {\n    return null;\n  }\n\n  const url = new URL(options.request.url);\n  const hasMarkdownExtension = url.pathname.toLowerCase().endsWith(\".md\");\n  if (!hasMarkdownExtension && !requestAcceptsMarkdown(options.request.headers.get(\"accept\"))) {\n    return null;\n  }\n\n  const targetPathname = normalizeFarmMarkdownRoutePath(url.pathname);\n  const source = await options.resolveSource(targetPathname);\n  if (!source) {\n    return null;\n  }\n\n  const headers = new Headers({\n    \"Content-Type\": \"text/markdown; charset=utf-8\",\n    \"Content-Location\": targetPathname === \"/\" ? \"/index.md\" : `${targetPathname}.md`,\n    \"Cache-Control\": \"public, s-maxage=60, stale-while-revalidate=300\",\n    \"X-Farm-Markdown-Route\": targetPathname,\n    \"X-Farm-Markdown-Source\": source.filePath,\n  });\n  if (!hasMarkdownExtension) {\n    headers.set(\"Vary\", \"Accept\");\n  }\n\n  return new Response(options.request.method === \"HEAD\" ? null : source.source, {\n    status: 200,\n    headers,\n  });\n}\n","export type FarmMarkdownRouteInput =\n  | string\n  | {\n      route: string;\n      title?: string;\n      cache?: number | false;\n    };\n\nexport interface FarmMarkdownUserConfig {\n  /**\n   * Enable generated markdown representations for React pages.\n   * @default true\n   */\n  enabled?: boolean;\n  /**\n   * Routes that may expose generated markdown. Every page is exposed by default.\n   */\n  expose?: boolean | FarmMarkdownRouteInput[];\n  /** @deprecated Use `expose`. */\n  routes?: FarmMarkdownRouteInput[];\n  cache?: number | false;\n  includeMetadata?: boolean;\n}\n\nexport interface FarmMarkdownResolvedRoute {\n  route: string;\n  title?: string;\n  cache?: number | false;\n}\n\nexport interface FarmMarkdownResolvedConfig {\n  enabled: boolean;\n  expose: true | FarmMarkdownResolvedRoute[];\n  cache: number | false;\n  includeMetadata: boolean;\n}\n\nexport interface FarmMarkdownMirrorTarget {\n  pathname: string;\n  route: FarmMarkdownResolvedRoute | null;\n}\n\nexport interface ResolveMarkdownMirrorTargetOptions {\n  accept?: string | null;\n}\n\nexport interface CreateMarkdownMirrorResponseOptions {\n  request: Request;\n  config?: FarmMarkdownResolvedConfig;\n  routeExists?: (pathname: string) => boolean;\n  renderPage: (request: Request) => Response | Promise<Response>;\n}\n\nexport interface ApplyMarkdownNegotiationHeadersOptions {\n  config?: FarmMarkdownResolvedConfig;\n  pathname: string;\n}\n\nexport function resolveMarkdownConfig(\n  config: FarmMarkdownUserConfig | boolean | undefined,\n): FarmMarkdownResolvedConfig {\n  if (config === false) {\n    return {\n      enabled: false,\n      expose: [],\n      cache: false,\n      includeMetadata: true,\n    };\n  }\n\n  if (config === true || config === undefined) {\n    return {\n      enabled: true,\n      expose: true,\n      cache: false,\n      includeMetadata: true,\n    };\n  }\n\n  const exposeInput = config.expose ?? config.routes ?? true;\n  const expose =\n    exposeInput === true\n      ? true\n      : Array.isArray(exposeInput)\n        ? exposeInput.map(normalizeMarkdownRouteInput)\n        : [];\n\n  return {\n    enabled: config.enabled !== false && (expose === true || expose.length > 0),\n    expose,\n    cache: config.cache ?? false,\n    includeMetadata: config.includeMetadata ?? true,\n  };\n}\n\nexport function resolveMarkdownMirrorTarget(\n  config: FarmMarkdownResolvedConfig | undefined,\n  pathname: string,\n  options: ResolveMarkdownMirrorTargetOptions = {},\n): FarmMarkdownMirrorTarget | null {\n  const hasMarkdownExtension = pathname.toLowerCase().endsWith(\".md\");\n  if (!config?.enabled || (!hasMarkdownExtension && !requestAcceptsMarkdown(options.accept))) {\n    return null;\n  }\n\n  const targetPathname = normalizeMarkdownRoute(\n    hasMarkdownExtension ? pathname.slice(0, -\".md\".length) || \"/\" : pathname,\n  );\n  const route = findExposedMarkdownRoute(config, targetPathname);\n  if (!route && config.expose !== true) {\n    return null;\n  }\n\n  return {\n    pathname: targetPathname,\n    route,\n  };\n}\n\nexport async function createMarkdownMirrorResponse(\n  options: CreateMarkdownMirrorResponseOptions,\n): Promise<Response | null> {\n  if (options.request.method !== \"GET\" && options.request.method !== \"HEAD\") {\n    return null;\n  }\n\n  const requestUrl = new URL(options.request.url);\n  const hasMarkdownExtension = requestUrl.pathname.toLowerCase().endsWith(\".md\");\n  const target = resolveMarkdownMirrorTarget(options.config, requestUrl.pathname, {\n    accept: options.request.headers.get(\"accept\"),\n  });\n  if (!target) {\n    return null;\n  }\n\n  if (options.routeExists && !options.routeExists(target.pathname)) {\n    return null;\n  }\n\n  const pageUrl = new URL(options.request.url);\n  pageUrl.pathname = target.pathname;\n  const headers = new Headers(options.request.headers);\n  headers.set(\"accept\", \"text/html\");\n\n  const pageResponse = await options.renderPage(\n    new Request(pageUrl, {\n      method: \"GET\",\n      headers,\n    }),\n  );\n\n  if (!isHtmlResponse(pageResponse)) {\n    return null;\n  }\n\n  const html = await pageResponse.text();\n  const markdown = htmlToMarkdown(html, {\n    title: target.route?.title,\n    includeMetadata: options.config?.includeMetadata ?? true,\n    sourcePath: target.pathname,\n  });\n  const headersOut = new Headers({\n    \"Content-Type\": \"text/markdown; charset=utf-8\",\n    \"Content-Location\": target.pathname === \"/\" ? \"/index.md\" : `${target.pathname}.md`,\n    \"X-Farm-Markdown-Route\": target.pathname,\n  });\n  if (!hasMarkdownExtension) {\n    headersOut.set(\"Vary\", \"Accept\");\n  }\n  const cache = target.route?.cache ?? options.config?.cache ?? false;\n  headersOut.set(\"Cache-Control\", createMarkdownCacheHeader(cache));\n\n  return new Response(options.request.method === \"HEAD\" ? null : markdown, {\n    status: pageResponse.status,\n    headers: headersOut,\n  });\n}\n\nexport function applyMarkdownNegotiationHeaders(\n  response: Response,\n  options: ApplyMarkdownNegotiationHeadersOptions,\n): Response {\n  if (!isHtmlResponse(response)) {\n    return response;\n  }\n\n  const target = resolveMarkdownMirrorTarget(options.config, options.pathname, {\n    accept: \"text/markdown\",\n  });\n  if (!target) {\n    return response;\n  }\n\n  const alternatePath = getMarkdownAlternatePath(target.pathname);\n  const headers = new Headers(response.headers);\n  appendHeaderToken(headers, \"Vary\", \"Accept\");\n  headers.append(\"Link\", `<${alternatePath}>; rel=\"alternate\"; type=\"text/markdown\"`);\n\n  return new Response(response.body, {\n    status: response.status,\n    statusText: response.statusText,\n    headers,\n  });\n}\n\nexport function htmlToMarkdown(\n  html: string,\n  options: {\n    title?: string;\n    sourcePath?: string;\n    includeMetadata?: boolean;\n  } = {},\n): string {\n  const title = options.title ?? extractHtmlTitle(html);\n  let source = extractHtmlBody(html)\n    .replace(/<script\\b[^>]*>[\\s\\S]*?<\\/script>/gi, \"\")\n    .replace(/<style\\b[^>]*>[\\s\\S]*?<\\/style>/gi, \"\")\n    .replace(/<noscript\\b[^>]*>[\\s\\S]*?<\\/noscript>/gi, \"\")\n    .replace(/<!--[\\s\\S]*?-->/g, \"\");\n\n  source = source.replace(/<pre\\b[^>]*><code\\b[^>]*>([\\s\\S]*?)<\\/code><\\/pre>/gi, (_, code) => {\n    return `\\n\\n\\`\\`\\`\\n${decodeHtml(stripTags(code)).trim()}\\n\\`\\`\\`\\n\\n`;\n  });\n  source = source.replace(/<pre\\b[^>]*>([\\s\\S]*?)<\\/pre>/gi, (_, code) => {\n    return `\\n\\n\\`\\`\\`\\n${decodeHtml(stripTags(code)).trim()}\\n\\`\\`\\`\\n\\n`;\n  });\n  source = source.replace(/<h([1-6])\\b[^>]*>([\\s\\S]*?)<\\/h\\1>/gi, (_, level, content) => {\n    return `\\n\\n${\"#\".repeat(Number(level))} ${toInlineMarkdown(content).trim()}\\n\\n`;\n  });\n  source = source.replace(/<p\\b[^>]*>([\\s\\S]*?)<\\/p>/gi, (_, content) => {\n    return `\\n\\n${toInlineMarkdown(content).trim()}\\n\\n`;\n  });\n  source = source.replace(/<blockquote\\b[^>]*>([\\s\\S]*?)<\\/blockquote>/gi, (_, content) => {\n    const quote = htmlToMarkdown(content, { includeMetadata: false })\n      .split(\"\\n\")\n      .filter((line) => line.trim().length > 0)\n      .map((line) => `> ${line}`)\n      .join(\"\\n\");\n    return `\\n\\n${quote}\\n\\n`;\n  });\n  source = source.replace(/<li\\b[^>]*>([\\s\\S]*?)<\\/li>/gi, (_, content) => {\n    return `\\n- ${toInlineMarkdown(content).trim()}`;\n  });\n  source = source\n    .replace(/<\\/?(ul|ol|main|section|article|header|footer|nav|aside|div)\\b[^>]*>/gi, \"\\n\")\n    .replace(/<br\\s*\\/?>/gi, \"\\n\")\n    .replace(/<hr\\s*\\/?>/gi, \"\\n\\n---\\n\\n\");\n\n  let markdown = stripTags(source)\n    .split(\"\\n\")\n    .map((line) => line.replace(/[ \\t]+$/g, \"\"))\n    .join(\"\\n\")\n    .replace(/\\n{3,}/g, \"\\n\\n\")\n    .trim();\n\n  if (options.includeMetadata !== false) {\n    const metadata: string[] = [];\n    if (title && !markdown.startsWith(\"# \")) {\n      metadata.push(`# ${title}`);\n    }\n    if (options.sourcePath) {\n      metadata.push(`Source: ${options.sourcePath}`);\n    }\n    if (metadata.length) {\n      markdown = `${metadata.join(\"\\n\\n\")}${markdown ? `\\n\\n${markdown}` : \"\"}`;\n    }\n  }\n\n  return `${markdown}\\n`;\n}\n\nfunction normalizeMarkdownRouteInput(input: FarmMarkdownRouteInput): FarmMarkdownResolvedRoute {\n  if (typeof input === \"string\") {\n    return {\n      route: normalizeMarkdownRoute(input),\n    };\n  }\n\n  return {\n    ...input,\n    route: normalizeMarkdownRoute(input.route),\n  };\n}\n\nfunction normalizeMarkdownRoute(route: string): string {\n  const withoutMarkdownExtension = route.toLowerCase().endsWith(\".md\") ? route.slice(0, -3) : route;\n  const withSlash = withoutMarkdownExtension.startsWith(\"/\")\n    ? withoutMarkdownExtension\n    : `/${withoutMarkdownExtension}`;\n  const normalized = withSlash.replace(/\\/+/g, \"/\").replace(/\\/$/g, \"\");\n  return normalized === \"\" || normalized === \"/index\" ? \"/\" : normalized;\n}\n\n/**\n * Quality value an `Accept` header assigns a media type, or 0 when the client\n * will not take it.\n *\n * `q=0` means \"not acceptable\" per RFC 9110, so it has to be distinguished from\n * an absent entry rather than treated as a match. Wildcards are opt-in: a client\n * sending `*&#47;*` will take anything, which says nothing about whether it would\n * rather have Markdown than HTML, so callers negotiating between two concrete\n * types should ask for exact entries only.\n */\nexport function farmAcceptQuality(\n  accept: string | null | undefined,\n  mediaType: string,\n  options: { wildcards?: boolean } = {},\n): number {\n  if (!accept) return 0;\n  const target = mediaType.toLowerCase();\n  const [targetType] = target.split(\"/\");\n  let best = 0;\n\n  for (const entry of accept.split(\",\")) {\n    const [candidate, ...parameters] = entry\n      .trim()\n      .toLowerCase()\n      .split(\";\")\n      .map((part) => part.trim());\n    if (!candidate) continue;\n\n    const matches =\n      candidate === target ||\n      (options.wildcards === true && (candidate === \"*/*\" || candidate === `${targetType}/*`));\n    if (!matches) continue;\n\n    const quality = parameters.find((parameter) => parameter.startsWith(\"q=\"));\n    const value = quality === undefined ? 1 : Number(quality.slice(2));\n    if (!Number.isFinite(value) || value <= 0) continue;\n    if (value > best) best = value;\n  }\n\n  return best;\n}\n\nexport function requestAcceptsMarkdown(accept: string | null | undefined): boolean {\n  if (!accept) {\n    return false;\n  }\n\n  return accept.split(\",\").some((entry) => {\n    const [mediaType, ...parameters] = entry\n      .trim()\n      .toLowerCase()\n      .split(\";\")\n      .map((part) => part.trim());\n    if (mediaType !== \"text/markdown\") {\n      return false;\n    }\n\n    const quality = parameters.find((parameter) => parameter.startsWith(\"q=\"));\n    return quality === undefined || Number(quality.slice(2)) > 0;\n  });\n}\n\nfunction getMarkdownAlternatePath(pathname: string): string {\n  return pathname === \"/\" ? \"/index.md\" : `${pathname}.md`;\n}\n\nfunction appendHeaderToken(headers: Headers, name: string, token: string): void {\n  const current = headers.get(name);\n  if (!current) {\n    headers.set(name, token);\n    return;\n  }\n\n  const tokens = current.split(\",\").map((value) => value.trim().toLowerCase());\n  if (!tokens.includes(token.toLowerCase())) {\n    headers.set(name, `${current}, ${token}`);\n  }\n}\n\nfunction findExposedMarkdownRoute(\n  config: FarmMarkdownResolvedConfig,\n  pathname: string,\n): FarmMarkdownResolvedRoute | null {\n  if (config.expose === true) {\n    return null;\n  }\n\n  return config.expose.find((route) => routeMatches(route.route, pathname)) ?? null;\n}\n\nfunction routeMatches(pattern: string, pathname: string): boolean {\n  if (pattern === pathname) {\n    return true;\n  }\n\n  const escaped = pattern\n    .split(\"/\")\n    .map((segment) => {\n      if (/^\\[\\.\\.\\.[^\\]]+\\]$/.test(segment)) {\n        return \".*\";\n      }\n      if (/^\\[[^\\]]+\\]$/.test(segment)) {\n        return \"[^/]+\";\n      }\n      return segment.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\");\n    })\n    .join(\"/\");\n\n  return new RegExp(`^${escaped}$`).test(pathname);\n}\n\nfunction isHtmlResponse(response: Response): boolean {\n  const contentType = response.headers.get(\"content-type\") ?? \"\";\n  return contentType.includes(\"text/html\");\n}\n\nfunction createMarkdownCacheHeader(cache: number | false): string {\n  if (cache === false || cache <= 0) {\n    return \"no-store\";\n  }\n\n  return `public, max-age=${cache}, s-maxage=${cache}`;\n}\n\nfunction extractHtmlTitle(html: string): string | undefined {\n  const match = html.match(/<title\\b[^>]*>([\\s\\S]*?)<\\/title>/i);\n  return match ? decodeHtml(stripTags(match[1])).trim() : undefined;\n}\n\nfunction extractHtmlBody(html: string): string {\n  const rootMatch = html.match(\n    /<div\\b[^>]*id=[\"']root[\"'][^>]*>([\\s\\S]*?)<\\/div>\\s*(?:<script|<\\/body>|$)/i,\n  );\n  const bodyMatch = html.match(/<body\\b[^>]*>([\\s\\S]*?)<\\/body>/i);\n  const body = rootMatch ? rootMatch[1] : bodyMatch ? bodyMatch[1] : html;\n  return extractFirstElementContent(body, [\"main\", \"article\"]) ?? body;\n}\n\nfunction extractFirstElementContent(html: string, tagNames: string[]): string | undefined {\n  for (const tagName of tagNames) {\n    const match = html.match(new RegExp(`<${tagName}\\\\b[^>]*>([\\\\s\\\\S]*?)<\\\\/${tagName}>`, \"i\"));\n    if (match) {\n      return match[1];\n    }\n  }\n\n  return undefined;\n}\n\nfunction toInlineMarkdown(html: string): string {\n  let source = html\n    .replace(/<a\\b[^>]*href=[\"']([^\"']+)[\"'][^>]*>([\\s\\S]*?)<\\/a>/gi, (_, href, text) => {\n      const label: string = toInlineMarkdown(text).trim() || href;\n      return `[${label}](${decodeHtml(href)})`;\n    })\n    .replace(\n      /<img\\b[^>]*src=[\"']([^\"']+)[\"'][^>]*alt=[\"']([^\"']*)[\"'][^>]*\\/?>/gi,\n      (_, src, alt) => {\n        return `![${decodeHtml(alt)}](${decodeHtml(src)})`;\n      },\n    )\n    .replace(/<strong\\b[^>]*>([\\s\\S]*?)<\\/strong>/gi, (_, content) => {\n      return `**${toInlineMarkdown(content).trim()}**`;\n    })\n    .replace(/<b\\b[^>]*>([\\s\\S]*?)<\\/b>/gi, (_, content) => {\n      return `**${toInlineMarkdown(content).trim()}**`;\n    })\n    .replace(/<em\\b[^>]*>([\\s\\S]*?)<\\/em>/gi, (_, content) => {\n      return `_${toInlineMarkdown(content).trim()}_`;\n    })\n    .replace(/<i\\b[^>]*>([\\s\\S]*?)<\\/i>/gi, (_, content) => {\n      return `_${toInlineMarkdown(content).trim()}_`;\n    })\n    .replace(/<code\\b[^>]*>([\\s\\S]*?)<\\/code>/gi, (_, content) => {\n      return `\\`${decodeHtml(stripTags(content)).trim()}\\``;\n    })\n    .replace(/<br\\s*\\/?>/gi, \"\\n\");\n\n  source = stripTags(source);\n  return decodeHtml(source).replace(/\\s+/g, \" \");\n}\n\nfunction stripTags(input: string): string {\n  return input.replace(/<[^>]+>/g, \"\");\n}\n\nfunction decodeHtml(input: string): string {\n  return input.replace(/&(?:nbsp|amp|lt|gt|quot|#39|#(?:x|X)[0-9a-fA-F]+|#[0-9]+);/g, (entity) => {\n    switch (entity) {\n      case \"&nbsp;\":\n        return \" \";\n      case \"&amp;\":\n        return \"&\";\n      case \"&lt;\":\n        return \"<\";\n      case \"&gt;\":\n        return \">\";\n      case \"&quot;\":\n        return '\"';\n      case \"&#39;\":\n        return \"'\";\n    }\n\n    const hexadecimal = entity[2] === \"x\" || entity[2] === \"X\";\n    const codePoint = Number.parseInt(entity.slice(hexadecimal ? 3 : 2, -1), hexadecimal ? 16 : 10);\n    return codePoint === 0 || codePoint > 0x10ffff || (codePoint >= 0xd800 && codePoint <= 0xdfff)\n      ? \"\\uFFFD\"\n      : String.fromCodePoint(codePoint);\n  });\n}\n","import type React from \"react\";\n\nexport type FarmMdxComponent = React.ComponentType<any> | keyof React.JSX.IntrinsicElements;\nexport type FarmMdxComponents = Record<string, FarmMdxComponent>;\n\nexport interface FarmMdxUserConfig {\n  /**\n   * Component map or module path that exports `components` or a default component map.\n   * Relative paths resolve from the project root.\n   */\n  components?: string | FarmMdxComponents;\n  /**\n   * Serve source-authored markdown pages at `/route.md`.\n   * Enabled by default for `page.md` and `page.mdx`.\n   */\n  markdownRoutes?: boolean;\n  /** Class name used for the wrapper around rendered markdown content. */\n  className?: string;\n}\n\nexport interface FarmMdxResolvedConfig {\n  components?: string | FarmMdxComponents;\n  markdownRoutes: boolean;\n  className: string;\n}\n\nexport function resolveMdxConfig(config: FarmMdxUserConfig | undefined): FarmMdxResolvedConfig {\n  return {\n    components: config?.components,\n    markdownRoutes: config?.markdownRoutes ?? true,\n    className: config?.className ?? \"farm-markdown\",\n  };\n}\n"],"mappings":"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","names":["path"]}