import { existsSync } from 'node:fs' import { readBoundedText } from '../../lib/bounded-text.js' import { containedProjectPath } from '../../lib/paths.js' import { parseStaticJsObject } from '../../lib/static-js-literal.js' import { optionString, parseFrontmatter, routeSlug, scanMarkdownDocs, type HumanAdapter, } from './core.js' type SidebarDocFilter = { readonly ids: ReadonlySet readonly autogenDirs: readonly string[] readonly enabled: boolean } const docusaurusFileSlug = (relPath: string): string => { const parts = relPath.split('/') const file = parts.at(-1) ?? '' const base = file.replace(/\.mdx?$/, '') const parent = parts.at(-2) if (/^(index|readme)$/i.test(base) || base === parent) return parts.slice(0, -1).join('/') return routeSlug(relPath) } const docusaurusSlug = (relPath: string, raw: string): string => { const slug = parseFrontmatter(raw).slug if (!slug) return docusaurusFileSlug(relPath) return slug.replace(/^\.\//, '').replace(/^\//, '').replace(/\/$/, '') } const docusaurusSidebarId = (relPath: string, raw: string): string => { const id = parseFrontmatter(raw).id if (!id) return routeSlug(relPath) const parts = relPath.split('/').slice(0, -1) return [...parts, id].filter(Boolean).join('/') } const docusaurusRecordId = (relPath: string, raw: string): string => { const frontmatter = parseFrontmatter(raw) return frontmatter.package ?? frontmatter.module ?? docusaurusSidebarId(relPath, raw) } const readSidebars = (sidebarsFile: string | undefined): SidebarDocFilter => { if (!sidebarsFile) return { enabled: false, ids: new Set(), autogenDirs: [] } if (!existsSync(sidebarsFile)) return { enabled: false, ids: new Set(), autogenDirs: [] } const raw = readBoundedText(sidebarsFile, { used: 0 }, { maxFileBytes: 1_048_576, maxCorpusBytes: 1_048_576 }) const filter = { ids: new Set(), autogenDirs: [] as string[] } const visit = (value: unknown): void => { if (typeof value === 'string') { filter.ids.add(value) return } if (Array.isArray(value)) { for (const item of value) visit(item) return } if (!value || typeof value !== 'object') return const item = value as Record if ((item.type === 'doc' || item.type === 'ref') && typeof item.id === 'string') filter.ids.add(item.id) if (item.type === 'autogenerated' && typeof item.dirName === 'string') filter.autogenDirs.push(item.dirName) if (Array.isArray(item.items)) visit(item.items) if (item.link && typeof item.link === 'object') visit(item.link) for (const child of Object.values(item)) { if (Array.isArray(child)) visit(child) } } visit(parseStaticJsObject(raw)) return { enabled: true, ...filter } } const isIncludedBySidebar = (filter: SidebarDocFilter, id: string): boolean => { if (!filter.enabled) return true if (filter.ids.has(id)) return true return filter.autogenDirs.some((dir) => { const prefix = dir === '.' ? '' : dir.replace(/\/$/, '') return prefix ? id === prefix || id.startsWith(`${prefix}/`) : true }) } export const docusaurusAdapter: HumanAdapter = { plugin: 'docusaurus', scan: ({ root, config }) => { const docsDir = optionString(config.options, ['docsDir', 'root']) if (!docsDir) return [] const sidebarsFile = optionString(config.options, ['sidebarsFile']) const sidebarFilter = readSidebars(sidebarsFile ? containedProjectPath(root, sidebarsFile) : undefined) return scanMarkdownDocs(root, docsDir, { includeRelPath: (relPath, raw) => isIncludedBySidebar(sidebarFilter, docusaurusSidebarId(relPath, raw)), idForDoc: docusaurusRecordId, slugForDoc: docusaurusSlug, urlPrefix: config.options?.urlPrefix, }) }, }