UNPKG

@graphql-markdown/core

Version:

GraphQL-Markdown core package for generating Markdown documentation from a GraphQL schema.

773 lines (772 loc) 33.2 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.getRenderer = exports.Renderer = exports.getApiGroupFolder = exports.API_GROUPS = void 0; const node_path_1 = require("node:path"); const graphql_1 = require("@graphql-markdown/graphql"); const utils_1 = require("@graphql-markdown/utils"); const logger_1 = require("@graphql-markdown/logger"); const config_1 = require("./config"); const validation_1 = require("./directives/validation"); const event_emitter_1 = require("./event-emitter"); const events_1 = require("./events"); /** * Constant representing the string literal for deprecated entities. * Used for grouping deprecated schema entities when the deprecated option is set to "group". * * @example * ```typescript * // When using the deprecated grouping option * if (isDeprecated(type)) { * const groupName = DEPRECATED; * // Handle deprecated type * } * ``` */ const DEPRECATED = "deprecated"; /** * CSS class names used for styling different categories in the generated documentation. * These are applied to the generated index metafiles for different section types. * * @example * ```typescript * // When generating API section metafiles * await generateIndexMetafile(dirPath, typeCat, { * styleClass: CATEGORY_STYLE_CLASS.API * }); * ``` */ var CATEGORY_STYLE_CLASS; (function (CATEGORY_STYLE_CLASS) { /** * CSS class applied to API sections (operations) */ CATEGORY_STYLE_CLASS["API"] = "graphql-markdown-api-section"; /** * CSS class applied to deprecated entity sections */ CATEGORY_STYLE_CLASS["DEPRECATED"] = "graphql-markdown-deprecated-section"; })(CATEGORY_STYLE_CLASS || (CATEGORY_STYLE_CLASS = {})); /** * Enum defining sidebar position values for ordering categories in the documentation sidebar. * Used to control the relative position of categories in the navigation. * * @example * ```typescript * // Position deprecated entities at the end of the sidebar * await generateIndexMetafile(dirPath, DEPRECATED, { * sidebarPosition: SidebarPosition.LAST * }); * ``` */ var SidebarPosition; (function (SidebarPosition) { /** * Position at the beginning of the sidebar */ SidebarPosition[SidebarPosition["FIRST"] = 1] = "FIRST"; /** * Position at the end of the sidebar */ SidebarPosition[SidebarPosition["LAST"] = 999] = "LAST"; })(SidebarPosition || (SidebarPosition = {})); /** * Default group names for API types and non-API types. * This constant provides the base folder structure for organizing GraphQL schema entities. * Can be overridden via ApiGroupOverrideType in configuration. * * @property operations Folder name for GraphQL operations (queries, mutations, subscriptions) * @property types Folder name for GraphQL type definitions * * @example * ```typescript * // Default structure * const defaultGroups = API_GROUPS; * // { operations: "operations", types: "types" } * * // With custom override * const customGroups = { ...API_GROUPS, operations: "queries-and-mutations" }; * ``` * * @see {@link getApiGroupFolder} For usage with type categorization */ exports.API_GROUPS = { operations: "operations", types: "types", }; /** * Determines the appropriate folder for a GraphQL schema entity based on its type. * * @param type - The GraphQL schema entity to categorize * @param groups - Optional custom group naming configuration * @returns The folder name where the entity should be placed * * @example * ```typescript * // With default groups * const folder = getApiGroupFolder(queryType); // Returns "operations" * * // With custom groups * const folder = getApiGroupFolder(objectType, { operations: "queries" }); // Returns appropriate folder * ``` */ const getApiGroupFolder = (type, groups) => { let folderNames = exports.API_GROUPS; if ((0, validation_1.isGroupsObject)(groups)) { folderNames = { ...exports.API_GROUPS, ...groups }; } return (0, graphql_1.isApiType)(type) ? folderNames.operations : folderNames.types; }; exports.getApiGroupFolder = getApiGroupFolder; /** * Strips numeric prefix from a folder name if categorySort is enabled. * Converts folder names like "01-query" back to "query" for category identification. * * This is needed when extracting category names from file paths that were created * with categorySort enabled. The regex matches leading two-digit numbers * followed by a hyphen. * * @param folderName - The folder name to strip (e.g., "01-query", "02-mutations") * @returns The folder name without prefix (e.g., "query", "mutations") * * @example * ```typescript * stripNumericPrefix("01-query"); // Returns "query" * stripNumericPrefix("02-mutations"); // Returns "mutations" * stripNumericPrefix("objects"); // Returns "objects" (no prefix to strip) * ``` */ const stripNumericPrefix = (folderName) => { return folderName.replace(/^\d{2}-/, ""); }; /** * Type guard function that checks if the provided options include a specific hierarchy configuration. * * @param options - The renderer options to check * @param hierarchy - The hierarchy type to check for * @returns True if the options contain the specified hierarchy configuration * * @example * ```typescript * if (isHierarchy(options, TypeHierarchy.FLAT)) { * // Handle flat hierarchy structure * } * ``` */ const isHierarchy = (options, hierarchy) => { return options?.hierarchy?.[hierarchy] !== undefined; }; /** * Default natural sorting function for categories. * Sorts categories alphabetically using localeCompare. * * @param a - First category name * @param b - Second category name * @returns Comparison result for sorting */ const naturalSort = (a, b) => { return a.localeCompare(b); }; /** * Manages category positions for the sidebar. * Supports two modes: * 1. Pre-registration: categories are registered upfront, positions computed once * 2. On-demand: positions are computed as categories are encountered * */ class CategoryPositionManager { categories = new Set(); positionCache = new Map(); positionsComputed = false; sortFn; basePosition; /** * Creates a new CategoryPositionManager. * * @param sortFn - Function to sort categories (defaults to natural/alphabetical) * @param basePosition - Starting position for categories (defaults to 1) */ constructor(sortFn, basePosition = 1) { this.sortFn = sortFn === "natural" || !sortFn ? naturalSort : sortFn; this.basePosition = basePosition; } /** * Pre-registers a batch of category names. * This should be called before rendering to ensure consistent positioning. * * @param categoryNames - Array of category names to register */ registerCategories(categoryNames) { for (const name of categoryNames) { this.categories.add(name); } } /** * Computes positions for all registered categories. * This is called either manually after registration or automatically on first getPosition call. */ computePositions() { if (this.positionsComputed) { return; } const sorted = Array.from(this.categories).sort(this.sortFn); for (let index = 0; index < sorted.length; index++) { this.positionCache.set(sorted[index], this.basePosition + index); } this.positionsComputed = true; } /** * Gets the assigned position for a category. * If category positions haven't been computed yet, computes them first. * * @param category - The category name * @returns The position assigned to this category or basePosition if not found */ getPosition(category) { // Ensure positions are computed first (uses pre-registered categories) if (!this.positionsComputed) { this.computePositions(); } // Return cached position or base position if not found // NOTE: We don't dynamically add categories here to avoid breaking // pre-computed positions. This ensures consistent positioning even if // getPosition is called for categories that weren't pre-registered. return this.positionCache.get(category) ?? this.basePosition; } /** * Check if a category was pre-registered without triggering recomputation. * Used to determine hierarchy level (root vs nested) without side effects. * * @param category - Category name to check * @returns true if category was pre-registered */ isRegistered(category) { return this.categories.has(category); } } /** * Core renderer class responsible for generating documentation files from GraphQL schema entities. * Handles the conversion of schema types to markdown/MDX documentation with proper organization. * * HIERARCHY LEVELS WHEN categorySort IS ENABLED: * - Level 0 (root): Query, Mutation, Subscription, Custom Groups → 01-Query, 02-Mutation, etc. * - Level 1 (under root): Specific types within each root → 01-Objects, 02-Enums, etc. * * Each level has its own CategoryPositionManager that restarts numbering at 1. * @example */ class Renderer { group; outputDir; baseURL; prettify; options; mdxExtension; // mdxModuleIndexFileSupport: boolean; printer; rootLevelPositionManager; categoryPositionManager; /** * Creates a new Renderer instance. * * @param printer - The printer instance used to convert GraphQL types to markdown * @param outputDir - Directory where documentation will be generated * @param baseURL - Base URL for the documentation * @param group - Optional grouping configuration for schema entities * @param prettify - Whether to format the generated markdown * @param docOptions - Additional documentation options * @param mdxExtension - Optional MDX file extension to use * @example */ constructor(printer, outputDir, baseURL, group, prettify, docOptions, mdxExtension) { this.printer = printer; this.group = group; this.outputDir = outputDir; this.baseURL = baseURL; this.prettify = prettify; this.options = docOptions; this.mdxExtension = mdxExtension; // Initialize position managers for different hierarchy levels // rootLevelPositionManager: for root-level categories (Query, Mutation, Deprecated, etc.) this.rootLevelPositionManager = new CategoryPositionManager(docOptions?.categorySort, 1); // categoryPositionManager: for categories within each root type (e.g., under Query) this.categoryPositionManager = new CategoryPositionManager(docOptions?.categorySort, 1); } /** * Generates an index metafile for a category directory if MDX support is available. * * @param dirPath - The directory path where the index should be created * @param category - The category name * @param options - Configuration options for the index * @returns Promise that resolves when the index is generated * * @example * ```typescript * await renderer.generateIndexMetafile('docs/types', 'Types', { * collapsible: true, * collapsed: false * }); * ``` */ async generateIndexMetafile(dirPath, category, options) { const defaultOptions = { collapsible: true, collapsed: true, }; const finalOptions = options ?? defaultOptions; const sidebarPosition = finalOptions.sidebarPosition ?? this.categoryPositionManager.getPosition(category); const events = (0, event_emitter_1.getEvents)(); const event = new events_1.GenerateIndexMetafileEvent({ dirPath, category, options: { ...finalOptions, sidebarPosition, index: this.options?.index, }, }); const handlerErrors = await events.emitAsync(events_1.GenerateIndexMetafileEvents.BEFORE_GENERATE, event); if (Array.isArray(handlerErrors) && handlerErrors.length > 0) { handlerErrors.forEach((error) => { (0, logger_1.log)(`Error handler for ${events_1.GenerateIndexMetafileEvents.BEFORE_GENERATE}: ${error.message}`, logger_1.LogLevel.error); }); } await events.emitAsync(events_1.GenerateIndexMetafileEvents.AFTER_GENERATE, new events_1.GenerateIndexMetafileEvent({ dirPath, category })); } /** * Generates the directory path and metafiles for a specific schema entity type. * Creates the appropriate directory structure based on configuration options. * * @param type - The schema entity type * @param name - The name of the schema entity * @param rootTypeName - The root type name this entity belongs to * @returns The generated directory path * @example */ async generateCategoryMetafileType(type, name, rootTypeName) { let dirPath = this.outputDir; if (!(0, validation_1.isPath)(dirPath)) { throw new Error("Output directory is empty or not specified"); } if (isHierarchy(this.options, config_1.TypeHierarchy.FLAT)) { return dirPath; } // Deprecated gets highest priority - at ROOT level before even custom groups if (this.options?.deprecated === "group" && (0, graphql_1.isDeprecated)(type)) { const formattedDeprecated = this.formatCategoryFolderName(DEPRECATED, true); dirPath = (0, node_path_1.join)(dirPath, formattedDeprecated); await this.generateIndexMetafile(dirPath, DEPRECATED, { sidebarPosition: SidebarPosition.LAST, styleClass: CATEGORY_STYLE_CLASS.DEPRECATED, }); } // Custom groups come after deprecated but before hierarchy levels if (this.group && rootTypeName in this.group && name in this.group[rootTypeName]) { const rootGroup = this.group[rootTypeName][name] ?? ""; // Custom groups are always at ROOT level (they come after deprecated in hierarchy priority) const formattedRootGroup = this.formatCategoryFolderName(rootGroup, true); dirPath = (0, node_path_1.join)(dirPath, formattedRootGroup); await this.generateIndexMetafile(dirPath, rootGroup); } const useApiGroup = isHierarchy(this.options, config_1.TypeHierarchy.API) ? this.options.hierarchy[config_1.TypeHierarchy.API] : !isHierarchy(this.options, config_1.TypeHierarchy.ENTITY); if (useApiGroup) { const typeCat = (0, exports.getApiGroupFolder)(type, useApiGroup); // API groups are root-level if no custom groups exist, nested if custom groups exist const isApiGroupRootLevel = !this.group; const formattedTypeCat = this.formatCategoryFolderName(typeCat, isApiGroupRootLevel); dirPath = (0, node_path_1.join)(dirPath, formattedTypeCat); await this.generateIndexMetafile(dirPath, typeCat, { collapsible: false, collapsed: false, styleClass: CATEGORY_STYLE_CLASS.API, }); } // Entity categories are: // - Root-level in entity hierarchy (only when no custom groups exist) // - Nested in API hierarchy // - Nested in entity hierarchy when custom groups exist const isRootTypeLevelCat = useApiGroup ? false : !this.group; const formattedRootTypeName = this.formatCategoryFolderName(rootTypeName, isRootTypeLevelCat); dirPath = (0, node_path_1.join)(dirPath, formattedRootTypeName); await this.generateIndexMetafile(dirPath, rootTypeName); return dirPath; } /** * Renders all types within a root type category (e.g., all Query types). * * @param rootTypeName - The name of the root type (e.g., "Query", "Mutation") * @param type - The type object containing all entities to render * @returns Array of rendered categories or undefined * @example */ async renderRootTypes(rootTypeName, type) { if (typeof type !== "object" || type === null) { return undefined; } const isFlat = isHierarchy(this.options, config_1.TypeHierarchy.FLAT); const isOperationRootType = rootTypeName === "queries" || rootTypeName === "mutations" || rootTypeName === "subscriptions"; return Promise.all(Object.keys(type).map(async (name) => { let dirPath = this.outputDir; let entityName = name; let operationNamespaceParts; if (isOperationRootType && name.includes(".")) { const namespaceParts = name.split(".").filter(Boolean); if (namespaceParts.length > 1) { operationNamespaceParts = namespaceParts.slice(0, -1); if (!isFlat) { entityName = namespaceParts.at(-1) ?? entityName; } } } if (!isFlat) { dirPath = await this.generateCategoryMetafileType(type[name], name, rootTypeName); if (operationNamespaceParts && operationNamespaceParts.length > 0) { for (const namespace of operationNamespaceParts) { const formattedNamespace = (0, utils_1.slugify)(namespace); dirPath = (0, node_path_1.join)(dirPath, formattedNamespace); await this.generateIndexMetafile(dirPath, namespace); } } } return this.renderTypeEntities(dirPath, entityName, type[name], operationNamespaceParts); })); } /** * Renders documentation for a specific type entity and saves it to a file. * * @param dirPath - The directory path where the file should be saved * @param name - The name of the type entity * @param type - The type entity to render * @returns The category information for the rendered entity or undefined * @example */ async renderTypeEntities(dirPath, name, type, operationNamespaceParts) { if (!(0, validation_1.isPath)(dirPath)) { throw new Error("Output directory is empty or not specified"); } // Regex patterns to parse page metadata from file paths // Matches: path/to/category/page-name.md(x) or path\to\category\page-name.md(x) // Only allows alphanumeric and hyphens in category and page names // NOSONAR - pattern is safe, hyphen in character class doesn't cause backtracking const PageRegex = /(?<category>[a-z0-9-]+)[\\/]+(?<pageId>[a-z0-9-]+)\.mdx?$/i; // NOSONAR const PageRegexFlat = /(?<pageId>[a-z0-9-]+)\.mdx?$/i; // NOSONAR const fileName = (0, utils_1.slugify)(name); const filePath = (0, node_path_1.join)((0, node_path_1.normalize)(dirPath), `${fileName}${this.mdxExtension}`); let content; try { const printOptions = { ...this.options, formatCategoryFolderName: (categoryName) => { // Resolve root-level category names first. Group names can be stored in // their original case in pre-collected categories, while link generation // may provide slugified lowercase variants. const rootCategory = this.rootLevelPositionManager.isRegistered(categoryName) ? categoryName : (0, utils_1.startCase)(categoryName); if (this.rootLevelPositionManager.isRegistered(rootCategory)) { return this.formatCategoryFolderName(rootCategory, true); } // Nested categories can also be registered in either raw or start-cased // forms depending on hierarchy inputs. if (this.categoryPositionManager.isRegistered(categoryName)) { return this.formatCategoryFolderName(categoryName, false); } const nestedCategory = (0, utils_1.startCase)(categoryName); if (this.categoryPositionManager.isRegistered(nestedCategory)) { return this.formatCategoryFolderName(nestedCategory, false); } // Unknown categories should not receive synthetic numeric prefixes. return (0, utils_1.slugify)(categoryName); }, ...(operationNamespaceParts === undefined ? {} : { operationNamespaceParts }), }; content = await this.printer.printType(fileName, type, printOptions); if (typeof content !== "string" || content === "") { (0, logger_1.log)(`No content generated for "${name}" - printType returned empty`, logger_1.LogLevel.warn); return undefined; } } catch (error) { (0, logger_1.log)(`An error occurred while processing "${name}": ${error instanceof Error ? error.message : String(error)}`, logger_1.LogLevel.warn); return undefined; } await (0, utils_1.saveFile)(filePath, content, this.prettify ? utils_1.prettifyMarkdown : undefined); const pagePath = (0, node_path_1.relative)(this.outputDir, filePath); const isFlat = isHierarchy(this.options, config_1.TypeHierarchy.FLAT); const page = isFlat ? PageRegexFlat.exec(pagePath) : PageRegex.exec(pagePath); if (!page?.groups) { (0, logger_1.log)(`An error occurred while processing file ${filePath} for type "${type}"`, logger_1.LogLevel.warn); return undefined; } // Strip numeric prefix from category if it was applied when categorySort is enabled const extractedCategory = isFlat ? page.groups.pageId : stripNumericPrefix(page.groups.category); const category = isFlat ? "schema" : (0, utils_1.startCase)(extractedCategory); const events = (0, event_emitter_1.getEvents)(); const event = new events_1.RenderTypeEntitiesEvent({ baseURL: this.baseURL, name, filePath, outputDir: this.outputDir, }); await events.emitAsync(events_1.RenderTypeEntitiesEvents.AFTER_RENDER, event); return { category, filePath, name, }; } /** * Pre-collects all category names that will be generated during rendering. * This allows the position manager to assign consistent positions before * any files are written. * * HIERARCHY LEVELS: * - Root level: Query, Mutation, Subscription, Deprecated (when grouped), custom root groups * - Nested level: operations/types (API groups), custom groups under roots * * CRITICAL: Categories registered must match the NAMES USED BY THE PRINTER * when generating links. The printer uses plural forms from ROOT_TYPE_LOCALE: * "operations", "objects", "directives", "enums", "inputs", "interfaces", * "mutations", "queries", "scalars", "subscriptions", "unions" * * NOT the folder names: "operations", "types" * * @param rootTypeNames - Array of root type names from the schema */ preCollectCategories(rootTypeNames) { const rootCategories = new Set(); const nestedCategories = new Set(); // Skip if flat hierarchy if (isHierarchy(this.options, config_1.TypeHierarchy.FLAT)) { return; } // Determine if using API hierarchy const useApiGroup = isHierarchy(this.options, config_1.TypeHierarchy.API) ? this.options.hierarchy[config_1.TypeHierarchy.API] : !isHierarchy(this.options, config_1.TypeHierarchy.ENTITY); // Register custom groups at root level this.registerCustomGroups(rootCategories); // Register categories based on hierarchy type if (useApiGroup) { this.registerApiGroupCategories(rootCategories, nestedCategories, this.group !== undefined); } else { this.registerEntityCategories(rootCategories, nestedCategories, rootTypeNames, this.group !== undefined); } // Deprecated category - when grouped, it goes to root level if (this.options?.deprecated === "group") { rootCategories.add(DEPRECATED); } // Register collected categories with position managers this.registerCategoriesWithManagers(rootCategories, nestedCategories); } /** * Renders the homepage for the documentation from a template file. * Replaces placeholders in the template with actual values. * * @param homepageLocation - Path to the homepage template file * @returns Promise that resolves when the homepage is rendered * @example */ async renderHomepage(homepageLocation) { if (typeof homepageLocation !== "string") { return; } if (!(0, validation_1.isPath)(this.outputDir)) { throw new Error("Output directory is empty or not specified"); } const homePage = (0, node_path_1.basename)(homepageLocation); const destLocation = (0, node_path_1.join)(this.outputDir, homePage); const slug = utils_1.pathUrl.resolve("/", this.baseURL); try { await (0, utils_1.copyFile)(homepageLocation, destLocation); const template = await (0, utils_1.readFile)(destLocation); const data = template .toString() .replaceAll("##baseURL##", slug) .replaceAll("##generated-date-time##", new Date().toLocaleString()); await (0, utils_1.saveFile)(destLocation, data, this.prettify ? utils_1.prettifyMarkdown : undefined); } catch (error) { (0, logger_1.log)(`An error occurred while processing the homepage ${homepageLocation}: ${error}`, logger_1.LogLevel.warn); } } /** * Registers custom group names from the current group configuration. * Custom groups are always registered at the ROOT level. * * @param rootCategories - Set to add custom group names to */ registerCustomGroups(rootCategories) { if (!this.group) { return; } for (const rootTypeName in this.group) { for (const name in this.group[rootTypeName]) { const groupName = this.group[rootTypeName][name]; if (groupName) { rootCategories.add(groupName); } } } } /** * Registers API group categories (operations/types) based on hierarchy and group configuration. * * @param rootCategories - Set to add root-level categories * @param nestedCategories - Set to add nested-level categories * @param hasCustomGroups - Whether custom groups are configured */ registerApiGroupCategories(rootCategories, nestedCategories, hasCustomGroups) { if (hasCustomGroups) { // Custom groups exist: operations/types are nested nestedCategories.add(exports.API_GROUPS.operations); nestedCategories.add(exports.API_GROUPS.types); } else { // No custom groups: operations/types are at ROOT level rootCategories.add(exports.API_GROUPS.operations); rootCategories.add(exports.API_GROUPS.types); } // Entity categories for API group - plural forms from ROOT_TYPE_LOCALE const entityCategoryNames = [ "directives", "enums", "inputs", "interfaces", "mutations", "objects", "queries", "scalars", "subscriptions", "unions", ]; for (const categoryName of entityCategoryNames) { nestedCategories.add(categoryName); } } /** * Registers entity hierarchy categories based on configuration. * * @param rootCategories - Set to add root-level categories * @param nestedCategories - Set to add nested-level categories * @param rootTypeNames - Array of root type names from schema * @param hasCustomGroups - Whether custom groups are configured */ registerEntityCategories(rootCategories, nestedCategories, rootTypeNames, hasCustomGroups) { for (const name of rootTypeNames) { if (hasCustomGroups) { // If custom groups exist, entity names go nested under them nestedCategories.add(name); } else { // If no custom groups, entity names are at root rootCategories.add(name); } } } /** * Registers collected categories with appropriate position managers. * Handles both hierarchical numbering (when categorySort is enabled) and traditional modes. * * @param rootCategories - Set of root-level categories * @param nestedCategories - Set of nested-level categories */ registerCategoriesWithManagers(rootCategories, nestedCategories) { const hasCategorySort = this.options?.categorySort !== undefined; if (hasCategorySort) { // Register with hierarchical numbering when categorySort is enabled this.rootLevelPositionManager.registerCategories(Array.from(rootCategories)); this.rootLevelPositionManager.computePositions(); this.categoryPositionManager.registerCategories(Array.from(nestedCategories)); this.categoryPositionManager.computePositions(); } else { // Traditional separate handling when categorySort is not defined this.rootLevelPositionManager.registerCategories(Array.from(rootCategories)); this.rootLevelPositionManager.computePositions(); if (nestedCategories.size > 0) { this.categoryPositionManager.registerCategories(Array.from(nestedCategories)); this.categoryPositionManager.computePositions(); } } } /** * Formats a category folder name, optionally prefixing with an order number. * * Supports hierarchical numbering: * - Root level items (Query, Mutation, etc.) use rootLevelPositionManager * - Nested items (groups, types within roots) use categoryPositionManager * * @param categoryName - The category name to format * @param isRootLevel - Whether this category is at the root level (default: false for nested) * @returns The formatted folder name (e.g., "01-objects" if prefix is enabled) * @private */ formatCategoryFolderName(categoryName, isRootLevel = false) { const hasCategorySort = this.options?.categorySort !== undefined; if (!hasCategorySort) { return (0, utils_1.slugify)(categoryName); } try { // Choose the appropriate position manager based on hierarchy level const manager = isRootLevel ? this.rootLevelPositionManager : this.categoryPositionManager; const position = manager.getPosition(categoryName); if (!position) { return (0, utils_1.slugify)(categoryName); } const paddedPosition = String(position).padStart(2, "0"); const slugifiedName = (0, utils_1.slugify)(categoryName); const result = `${paddedPosition}-${slugifiedName}`; return result; } catch { return (0, utils_1.slugify)(categoryName); } } } exports.Renderer = Renderer; /** * Factory function to create and initialize a Renderer instance. * Creates the output directory and returns a configured renderer. * * @param printer - The printer instance to use for rendering types * @param outputDir - The output directory for generated documentation * @param baseURL - The base URL for the documentation * @param group - Optional grouping configuration * @param prettify - Whether to prettify the output markdown * @param docOptions - Additional documentation options * @param mdxExtension - Extension to use for MDX files * @returns A configured Renderer instance * * @example * ```typescript * const renderer = await getRenderer( * myPrinter, * './docs', * '/api', * groupConfig, * true, * { force: true, index: true } * ); * ``` */ const getRenderer = async (printer, outputDir, baseURL, group, prettify, docOptions, mdxExtension) => { await (0, utils_1.ensureDir)(outputDir, { forceEmpty: docOptions?.force }); return new Renderer(printer, outputDir, baseURL, group, prettify, docOptions, mdxExtension); }; exports.getRenderer = getRenderer;