UNPKG

@mui/internal-docs-infra

Version:

MUI Infra - internal documentation creation tools.

157 lines (141 loc) 5.43 kB
import fs from 'node:fs/promises'; import path from 'node:path'; import { fileURLToPath } from 'node:url'; import { markdownToMetadata } from "../syncPageIndex/metadataToMarkdown.mjs"; /** * Options for creating a loadServerPageIndex function */ /** * Function type for loading page index data from a markdown file */ /** * Converts a path segment to a title * e.g., "docs-infra" -> "Docs Infra", "components" -> "Components" */ export function pathSegmentToTitle(segment) { return segment.split('-').map(word => word.charAt(0).toUpperCase() + word.slice(1)).join(' '); } /** * Recursively removes titleMarkdown fields from a heading hierarchy */ export function stripTitleMarkdown(hierarchy) { const result = {}; for (const [key, value] of Object.entries(hierarchy)) { if (typeof value === 'object' && value !== null) { const { titleMarkdown, children, ...rest } = value; const strippedChildren = children ? stripTitleMarkdown(children) : {}; result[key] = { ...rest, // Only include children if it has keys, otherwise set to undefined children: Object.keys(strippedChildren).length > 0 ? strippedChildren : undefined }; } } return result; } /** * Extracts prefix and title from an import path * e.g., "/path/to/app/docs-infra/components/page.mdx" -> { prefix: "/docs-infra/components/", title: "Docs Infra Components" } */ export function extractPrefixAndTitle(absolutePath, rootContext) { // Get the relative path from the root context const relativePath = path.relative(rootContext, absolutePath); // Extract the directory path (remove filename) const dirPath = path.dirname(relativePath); // Split into segments const allSegments = dirPath.split(path.sep); // Filter out segments: // - Remove leading 'src' and 'app' directory markers (only if they're at the start) // - Remove Next.js route groups (segments in parentheses like '(public)') // - Remove current directory markers ('.' and empty strings) const segments = allSegments.filter((seg, index) => { // Remove 'src' only if it's the first segment if (seg === 'src' && index === 0) { return false; } // Remove 'app' only if it's the first or second segment (after 'src') if (seg === 'app' && (index === 0 || index === 1 && allSegments[0] === 'src')) { return false; } // Filter out Next.js route groups (e.g., '(public)', '(content)') if (seg.startsWith('(') && seg.endsWith(')')) { return false; } // Filter out current directory markers if (seg === '.' || seg === '') { return false; } return true; }); // Generate prefix with leading and trailing slashes const prefix = segments.length > 0 ? `/${segments.join('/')}/` : '/'; // Generate title from path segments const title = segments.map(pathSegmentToTitle).join(' '); return { prefix, title }; } /** * Default loadServerPageIndex function that loads page index data from a markdown file. * This function uses process.cwd() as the root context for resolving relative paths. */ export const loadServerPageIndex = createLoadServerPageIndex(); /** * Creates a loadServerPageIndex function with custom options. * * This factory function creates a LoadServerPageIndex implementation that: * 1. Reads the markdown file from the provided file path * 2. Parses the markdown to extract metadata using markdownToMetadata * 3. Enriches the metadata with prefix and title derived from the file path * 4. Returns a SitemapSectionData object with page data * * @param options - Configuration options for the loader * @returns LoadServerPageIndex function that takes a file path and returns Promise<SitemapSectionData | null> */ export function createLoadServerPageIndex(options = {}) { const rootContext = options.rootContext ?? process.cwd(); return async function loadPageIndex(filePath) { // Convert file:// URLs to proper file system paths for reading the file // Using fileURLToPath handles Windows drive letters correctly (e.g., file:///C:/... → C:\...) const absolutePath = filePath.startsWith('file://') ? fileURLToPath(filePath) : filePath; // Extract prefix and title from the import path const { prefix, title: generatedTitle } = extractPrefixAndTitle(absolutePath, rootContext); // Read the markdown file const markdownContent = await fs.readFile(absolutePath, 'utf-8'); // Parse the markdown to extract metadata const metadata = await markdownToMetadata(markdownContent); if (!metadata) { return null; } // Add prefix and override title with the generated one from the path // Strip descriptionMarkdown and titleMarkdown to reduce bundle size const enrichedMetadata = { ...metadata, prefix, // Use the generated title from the path (override markdown's H1) title: generatedTitle, // Strip markdown AST fields from each page to reduce size pages: metadata.pages.map(page => { const { descriptionMarkdown, sections, ...pageWithoutMarkdown } = page; return { ...pageWithoutMarkdown, // Strip titleMarkdown from sections hierarchy sections: sections ? stripTitleMarkdown(sections) : undefined }; }) }; return enrichedMetadata; }; }