@mintlify/common
Version:
Commonly shared code within Mintlify
212 lines (211 loc) • 11.9 kB
JavaScript
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;
}