{"version":3,"sources":["../src/app-markdown.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"],"mappings":"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","names":[]}