UNPKG

@mintlify/common

Version:

Commonly shared code within Mintlify

212 lines (211 loc) 11.9 kB
import jsYaml from 'js-yaml'; import { OpenAPIV3 } from 'openapi-types'; import { optionallyAddLeadingSlash } from '../fs/optionallySlash.js'; import { slugToTitle } from '../slug/slugToTitle.js'; import { buildOpenApiMetaTag } from './buildOpenApiMetaTag.js'; import { prepareStringToBeValidFilename, generateUniqueFilenameWithoutExtension, } from './filenameUtils.js'; import { getOpenApiTitleAndDescription } from './getOpenApiTitleAndDescription.js'; import { getTagDisplayName } from './getTagDisplayName.js'; /** * Build MDX content string matching what createOpenApiFrontmatter produces in prebuild. * Includes title, description, openapi tag, version, deprecated, and all x-mint metadata * as proper YAML frontmatter. Browser-safe (no filesystem access). */ export function buildGeneratedMdxContent(page) { var _a, _b; // Build frontmatter object with fields in a consistent order const fm = {}; const metadata = page.xMintMetadata; // title: x-mint metadata takes priority, then page-level fm.title = (_a = (metadata && 'title' in metadata ? metadata.title : undefined)) !== null && _a !== void 0 ? _a : page.title; // description: x-mint metadata takes priority, then page-level const effectiveDescription = (_b = (metadata && 'description' in metadata ? metadata.description : undefined)) !== null && _b !== void 0 ? _b : page.description; if (effectiveDescription) { fm.description = effectiveDescription; } fm.openapi = page.openapi; // version: x-mint metadata takes priority, then page-level if (metadata && 'version' in metadata) { fm.version = metadata.version; } else if (page.version) { fm.version = page.version; } // deprecated: x-mint metadata takes priority, then page-level if (metadata && 'deprecated' in metadata) { fm.deprecated = metadata.deprecated; } else if (page.deprecated) { fm.deprecated = page.deprecated; } // All remaining x-mint metadata fields if (metadata) { const reserved = new Set(['openapi', 'version', 'deprecated', 'title', 'description']); for (const [key, value] of Object.entries(metadata)) { if (!reserved.has(key) && value !== undefined) { fm[key] = value; } } } const yamlStr = jsYaml .dump(fm, { lineWidth: -1, quotingType: "'", forceQuotes: false }) .trimEnd(); const frontmatter = `---\n${yamlStr}\n---`; return page.xMintContent ? `${frontmatter}\n\n${page.xMintContent}` : frontmatter; } const posixJoin = (...segments) => segments.join('/').replace(/\/+/g, '/').replace(/\/$/, '') || '/'; const posixResolve = (base, path) => { if (path.startsWith('/')) return path; return posixJoin(base, path); }; const DEFAULT_OUTPUT_DIR = 'api-reference'; function getXMintGroups(pathObject, operationObject) { var _a, _b; const groups = []; if (((_a = pathObject['x-mint']) === null || _a === void 0 ? void 0 : _a.groups) && Array.isArray(pathObject['x-mint'].groups)) { groups.push(...pathObject['x-mint'].groups); } if (((_b = operationObject === null || operationObject === void 0 ? void 0 : operationObject['x-mint']) === null || _b === void 0 ? void 0 : _b.groups) && Array.isArray(operationObject['x-mint'].groups)) { groups.push(...operationObject['x-mint'].groups); } return groups; } export function generateOpenApiPages(opts) { var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s; const { spec, specFilePath, outputDirectory } = opts; const outDir = outputDirectory !== null && outputDirectory !== void 0 ? outputDirectory : DEFAULT_OUTPUT_DIR; const openApiFilePathFromRoot = optionallyAddLeadingSlash(specFilePath); const pages = []; // Track used filenames for uniqueness (mirrors the nav array in processOpenApiPath) const usedFilenames = []; // eslint-disable-next-line @typescript-eslint/consistent-type-assertions const openApiFiles = [ { filename: (_a = specFilePath.split('/').pop()) !== null && _a !== void 0 ? _a : 'spec', spec: spec, originalFileLocation: specFilePath, }, ]; // Process paths for (const [apiPath, pathItemObject] of Object.entries(spec.paths)) { if (!pathItemObject || typeof pathItemObject !== 'object') continue; // eslint-disable-next-line @typescript-eslint/consistent-type-assertions const pathItem = pathItemObject; for (const method of Object.values(OpenAPIV3.HttpMethods)) { if (!(method in pathItem)) continue; // eslint-disable-next-line @typescript-eslint/consistent-type-assertions const operation = pathItem[method]; if (!operation) continue; if (operation['x-excluded']) continue; const xMint = operation['x-mint']; const xMintGroups = getXMintGroups(pathItem, operation); const tagName = (_b = operation.tags) === null || _b === void 0 ? void 0 : _b[0]; const groupName = getTagDisplayName(tagName, spec); let title = (_c = prepareStringToBeValidFilename(operation.summary)) !== null && _c !== void 0 ? _c : `${method}-${prepareStringToBeValidFilename(apiPath)}`; let folder = (_d = prepareStringToBeValidFilename(tagName)) !== null && _d !== void 0 ? _d : ''; let base = posixJoin(outDir, folder, title); if (xMint === null || xMint === void 0 ? void 0 : xMint.href) { const href = xMint.href.startsWith('/') ? xMint.href.slice(1) : xMint.href; const lastSlash = href.lastIndexOf('/'); title = lastSlash >= 0 ? href.slice(lastSlash + 1) : href; folder = lastSlash >= 0 ? href.slice(0, lastSlash) : ''; base = posixJoin(folder, title); } const filenameWithoutExtension = generateUniqueFilenameWithoutExtension(usedFilenames, base); usedFilenames.push(filenameWithoutExtension); const openapiMetaTag = buildOpenApiMetaTag({ filePath: openApiFilePathFromRoot, method, path: apiPath, }); const { title: titleTag, description } = getOpenApiTitleAndDescription(openApiFiles, openapiMetaTag); let xMintMetadata = xMint === null || xMint === void 0 ? void 0 : xMint.metadata; if (xMintGroups.length > 0) { xMintMetadata = Object.assign(Object.assign({}, xMintMetadata), { groups: [ ...(Array.isArray(xMintMetadata === null || xMintMetadata === void 0 ? void 0 : xMintMetadata.groups) ? xMintMetadata.groups : []), ...xMintGroups, ] }); } const page = { title: (_f = (_e = xMintMetadata === null || xMintMetadata === void 0 ? void 0 : xMintMetadata.title) !== null && _e !== void 0 ? _e : titleTag) !== null && _f !== void 0 ? _f : slugToTitle(filenameWithoutExtension), description: (_g = xMintMetadata === null || xMintMetadata === void 0 ? void 0 : xMintMetadata.description) !== null && _g !== void 0 ? _g : description, deprecated: (_h = xMintMetadata === null || xMintMetadata === void 0 ? void 0 : xMintMetadata.deprecated) !== null && _h !== void 0 ? _h : operation.deprecated, openapi: openapiMetaTag, href: posixResolve('/', filenameWithoutExtension), method, path: apiPath, tag: groupName, hidden: operation['x-hidden'] === true, xMintMetadata, xMintContent: xMint === null || xMint === void 0 ? void 0 : xMint.content, version: opts.version, }; pages.push(page); } } // Process webhooks (OpenAPI 3.1) // eslint-disable-next-line @typescript-eslint/consistent-type-assertions const specWithWebhooks = spec; // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- webhooks exists in OpenAPI 3.1 but not in the v3 types if (specWithWebhooks.webhooks) { for (const [webhook, webhookObject] of Object.entries(specWithWebhooks.webhooks)) { // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- defensive check for malformed specs if (!webhookObject || typeof webhookObject !== 'object') continue; // eslint-disable-next-line @typescript-eslint/consistent-type-assertions const pathItem = webhookObject; for (const method of Object.values(OpenAPIV3.HttpMethods)) { if (!(method in pathItem)) continue; // eslint-disable-next-line @typescript-eslint/consistent-type-assertions const operation = pathItem[method]; if (!operation) continue; if (operation['x-excluded']) continue; const xMint = operation['x-mint']; const tagName = (_j = operation.tags) === null || _j === void 0 ? void 0 : _j[0]; const groupName = getTagDisplayName(tagName, spec); let title = (_k = prepareStringToBeValidFilename(operation.summary)) !== null && _k !== void 0 ? _k : `${prepareStringToBeValidFilename(webhook)}`; let folder = (_l = prepareStringToBeValidFilename(tagName)) !== null && _l !== void 0 ? _l : ''; let base = posixJoin(outDir, folder, title); if (xMint === null || xMint === void 0 ? void 0 : xMint.href) { const href = xMint.href.startsWith('/') ? xMint.href.slice(1) : xMint.href; const lastSlash = href.lastIndexOf('/'); title = lastSlash >= 0 ? href.slice(lastSlash + 1) : href; folder = lastSlash >= 0 ? href.slice(0, lastSlash) : ''; base = posixJoin(folder, title); } const filenameWithoutExtension = generateUniqueFilenameWithoutExtension(usedFilenames, base); usedFilenames.push(filenameWithoutExtension); const openapiMetaTag = buildOpenApiMetaTag({ filePath: openApiFilePathFromRoot, method: 'webhook', path: webhook, }); const page = { title: (_o = (_m = xMint === null || xMint === void 0 ? void 0 : xMint.metadata) === null || _m === void 0 ? void 0 : _m.title) !== null && _o !== void 0 ? _o : slugToTitle(filenameWithoutExtension), description: (_q = (_p = xMint === null || xMint === void 0 ? void 0 : xMint.metadata) === null || _p === void 0 ? void 0 : _p.description) !== null && _q !== void 0 ? _q : operation.description, deprecated: (_s = (_r = xMint === null || xMint === void 0 ? void 0 : xMint.metadata) === null || _r === void 0 ? void 0 : _r.deprecated) !== null && _s !== void 0 ? _s : operation.deprecated, openapi: openapiMetaTag, href: posixResolve('/', filenameWithoutExtension), method: 'webhook', path: webhook, tag: groupName, hidden: operation['x-hidden'] === true, isWebhook: true, xMintMetadata: xMint === null || xMint === void 0 ? void 0 : xMint.metadata, xMintContent: xMint === null || xMint === void 0 ? void 0 : xMint.content, version: opts.version, }; pages.push(page); } } } return pages; }