@mintlify/common
Version:
Commonly shared code within Mintlify
543 lines (542 loc) • 22 kB
JavaScript
import { remove } from 'unist-util-remove';
import { CONTINUE, visit } from 'unist-util-visit';
import { isMdxJsEsm } from '../../utils.js';
const objectEstree = {
type: 'Program',
body: [
{
type: 'ExpressionStatement',
expression: {
type: 'ObjectExpression',
properties: [],
},
},
],
sourceType: 'module',
comments: [],
};
const arrayEstree = {
type: 'Program',
body: [
{
type: 'ExpressionStatement',
expression: {
type: 'ArrayExpression',
elements: [],
},
},
],
sourceType: 'module',
comments: [],
};
const falseEstree = {
type: 'Program',
body: [
{
type: 'ExpressionStatement',
expression: {
type: 'Literal',
value: false,
raw: 'false',
},
},
],
sourceType: 'module',
comments: [],
};
const trueEstree = {
type: 'Program',
body: [
{
type: 'ExpressionStatement',
expression: {
type: 'Literal',
value: true,
raw: 'true',
},
},
],
sourceType: 'module',
comments: [],
};
function createStringEstree(str) {
return {
type: 'Program',
body: [
{
type: 'ExpressionStatement',
expression: {
type: 'Literal',
value: str,
raw: `'${str}'`,
},
},
],
sourceType: 'module',
comments: [],
};
}
const numberEstree = {
type: 'Program',
body: [
{
type: 'ExpressionStatement',
expression: {
type: 'Literal',
value: 1,
raw: '1',
},
},
],
sourceType: 'module',
comments: [],
};
const DEFAULT_PROP_EXPRESSIONS = {
placement: { value: '"bottom"', estree: createStringEstree('bottom') },
deflectionEmail: { value: '""', estree: createStringEstree('') },
searchSites: { value: '[]', estree: arrayEstree },
spendLimit: { value: '1', estree: numberEstree },
sampleQuestions: { value: '[]', estree: arrayEstree },
slack: { value: 'false', estree: falseEstree },
subdomain: { value: '""', estree: createStringEstree('') },
context: { value: '{}', estree: objectEstree },
title: { value: '""', estree: createStringEstree('') },
description: { value: '""', estree: createStringEstree('') },
icon: { value: '""', estree: createStringEstree('') },
href: { value: '""', estree: createStringEstree('') },
iconType: { value: '"regular"', estree: createStringEstree('regular') },
enabled: { value: '"false"', estree: createStringEstree('false') },
name: { value: '""', estree: createStringEstree('') },
mcp: { value: '{}', estree: objectEstree },
openapi: { value: '""', estree: createStringEstree('') },
servers: { value: '[]', estree: arrayEstree },
securitySchemes: { value: '{}', estree: objectEstree },
security: { value: '{}', estree: objectEstree },
authMethod: { value: '"bearer"', estree: createStringEstree('bearer') },
playground: {
value: '"interactive"',
estree: createStringEstree('interactive'),
},
server: { value: '""', estree: createStringEstree('') },
metadata: { value: '{}', estree: objectEstree },
content: { value: '""', estree: createStringEstree('') },
codeSamples: { value: '[]', estree: arrayEstree },
lang: { value: '""', estree: createStringEstree('') },
label: { value: '""', estree: createStringEstree('') },
source: { value: '""', estree: createStringEstree('') },
examples: { value: '{}', estree: objectEstree },
summary: { value: '""', estree: createStringEstree('') },
value: { value: '{}', estree: objectEstree },
contextual: { value: '{}', estree: objectEstree },
api: { value: '{}', estree: objectEstree },
navigation: { value: '{}', estree: objectEstree },
asyncapi: { value: '""', estree: createStringEstree('') },
directory: { value: '""', estree: createStringEstree('') },
'x-hidden': { value: '"true"', estree: trueEstree },
'x-excluded': { value: '"true"', estree: trueEstree },
};
export function isStringSafe(value) {
switch (true) {
case value === 'true':
case value === 'false':
// purely alphanumeric string
case /^[_a-zA-Z0-9]+$/.test(value):
// string literal with quotes
case /^'[_a-zA-Z0-9\s`".\-\\${}?\(\)&*^%#@!~=+\[\]|:;,\/]+'$/.test(value):
case /^"[_a-zA-Z0-9\s`'.\-\\${}?\(\)&*^%#@!~=+\[\]|:;,\/]+"$/.test(value):
case /^`[_a-zA-Z0-9\s'".\-\\{}?\(\)&*^%#@!~=+\[\]|:;,\/]+`$/.test(value):
// purely alphanumeric string prefixed with `!!`
case /^!![_a-zA-Z0-9]+$/.test(value):
// purely alphanumeric string wrapped in parens
case /^\([_a-zA-Z0-9]+\)$/.test(value):
// purely alphanumeric string prefixed with `!!` wrapped in parens
case /^\(!![_a-zA-Z0-9]+\)$/.test(value):
return true;
// could contain arbitrary javascript
default:
return false;
}
}
function isStylePropertyValueSafe(property) {
if (property.computed)
return false;
const value = property.value;
if (value.type === 'Literal')
return true;
if (value.type === 'UnaryExpression' &&
value.operator === '-' &&
value.argument.type === 'Literal' &&
typeof value.argument.value === 'number') {
return true;
}
return false;
}
export function filterStyleProperties(estree) {
if (!estree.body.length)
return null;
const stmt = estree.body[0];
if ((stmt === null || stmt === void 0 ? void 0 : stmt.type) !== 'ExpressionStatement')
return null;
const expr = stmt.expression;
if (expr.type !== 'ObjectExpression')
return null;
const safeProperties = expr.properties.filter((prop) => prop.type === 'Property' && isStylePropertyValueSafe(prop));
if (safeProperties.length === 0)
return null;
const newExpr = Object.assign(Object.assign({}, expr), { properties: safeProperties });
return Object.assign(Object.assign({}, estree), { body: [
Object.assign(Object.assign({}, stmt), { expression: newExpr }),
] });
}
export function rebuildStyleValue(properties) {
const parts = properties.map((prop) => {
const key = prop.key.type === 'Identifier'
? prop.key.name
: prop.key.type === 'Literal'
? `'${String(prop.key.value).replace(/'/g, "\\'")}'`
: '';
let val = '';
if (prop.value.type === 'Literal') {
val =
typeof prop.value.value === 'string'
? `'${String(prop.value.value).replace(/'/g, "\\'")}'`
: String(prop.value.value);
}
else if (prop.value.type === 'UnaryExpression' &&
prop.value.operator === '-' &&
prop.value.argument.type === 'Literal') {
val = `-${prop.value.argument.value}`;
}
return `${key}: ${val}`;
});
return `{ ${parts.join(', ')} }`;
}
function isPrimitiveLiteralExpression(node) {
if (node.type === 'Literal')
return true;
return (node.type === 'UnaryExpression' &&
node.operator === '-' &&
typeof node.argument === 'object' &&
node.argument != null &&
node.argument.type === 'Literal');
}
function isSafeJsxAttributeValue(value) {
if (value == null)
return true;
if (typeof value !== 'object')
return false;
const node = value;
if (node.type === 'Literal')
return true;
if (node.type !== 'JSXExpressionContainer')
return false;
if (typeof node.expression !== 'object' || node.expression == null)
return false;
return isPrimitiveLiteralExpression(node.expression);
}
const DANGEROUS_ELEMENT_NAMES = new Set(['script', 'style', 'iframe', 'meta']);
function isSafeJsxElementName(name) {
if (typeof name !== 'object' || name == null)
return false;
const node = name;
if (node.type !== 'JSXIdentifier')
return false;
const elementName = node.name;
if (typeof elementName !== 'string')
return false;
return !DANGEROUS_ELEMENT_NAMES.has(elementName.toLowerCase());
}
function isEventHandlerAttribute(attributeNode) {
const name = attributeNode.name;
if (typeof name !== 'object' || name == null)
return false;
const nameNode = name;
if (typeof nameNode.name !== 'string')
return false;
return /^on[A-Z]/.test(nameNode.name);
}
function isSafeStaticJsxNode(node) {
if (node.type === 'JSXText')
return true;
if (node.type === 'JSXEmptyExpression')
return true;
if (node.type === 'JSXExpressionContainer') {
if (typeof node.expression !== 'object' || node.expression == null)
return false;
const expression = node.expression;
if (expression.type === 'JSXEmptyExpression')
return true;
return isPrimitiveLiteralExpression(expression);
}
if (node.type === 'JSXFragment') {
if (!Array.isArray(node.children))
return false;
return node.children.every((child) => typeof child === 'object' && child != null && isSafeStaticJsxNode(child));
}
if (node.type !== 'JSXElement')
return false;
if (typeof node.openingElement !== 'object' || node.openingElement == null)
return false;
const openingElement = node.openingElement;
if (!isSafeJsxElementName(openingElement.name))
return false;
if (!Array.isArray(openingElement.attributes))
return false;
const attributesAreSafe = openingElement.attributes.every((attribute) => {
if (typeof attribute !== 'object' || attribute == null)
return false;
const attributeNode = attribute;
if (attributeNode.type !== 'JSXAttribute')
return false;
if (isEventHandlerAttribute(attributeNode))
return false;
return isSafeJsxAttributeValue(attributeNode.value);
});
if (!attributesAreSafe)
return false;
if (!Array.isArray(node.children))
return false;
return node.children.every((child) => typeof child === 'object' && child != null && isSafeStaticJsxNode(child));
}
function isSafeStaticJsx(estree) {
const stmt = estree === null || estree === void 0 ? void 0 : estree.body[0];
if ((stmt === null || stmt === void 0 ? void 0 : stmt.type) !== 'ExpressionStatement')
return false;
return isSafeStaticJsxNode(stmt.expression);
}
function collectIdentifiersFromPattern(pattern, out) {
if (pattern.type === 'Identifier') {
out.push({ name: pattern.name, isFunction: false });
}
else if (pattern.type === 'ObjectPattern') {
for (const prop of pattern.properties) {
if (prop.type === 'Property') {
collectIdentifiersFromPattern(prop.value, out);
}
else {
collectIdentifiersFromPattern(prop.argument, out);
}
}
}
else if (pattern.type === 'ArrayPattern') {
for (const el of pattern.elements) {
if (el)
collectIdentifiersFromPattern(el, out);
}
}
else if (pattern.type === 'AssignmentPattern') {
collectIdentifiersFromPattern(pattern.left, out);
}
else if (pattern.type === 'RestElement') {
collectIdentifiersFromPattern(pattern.argument, out);
}
}
// Returns the identifier name if the estree expression is a bare reference that will be kept
// by isStringSafe but needs a stub to avoid a ReferenceError at render time:
// {foo} → Identifier
// {!!foo} → UnaryExpression(! UnaryExpression(! Identifier))
// Parenthesised forms are transparent in ESTree so (foo) produces the same AST as foo.
function extractIdentifierRef(estree) {
const stmt = estree === null || estree === void 0 ? void 0 : estree.body[0];
if ((stmt === null || stmt === void 0 ? void 0 : stmt.type) !== 'ExpressionStatement')
return null;
const expr = stmt.expression;
if (expr.type === 'Identifier')
return expr.name;
if (expr.type === 'UnaryExpression' &&
expr.operator === '!' &&
expr.argument.type === 'UnaryExpression' &&
expr.argument.operator === '!' &&
expr.argument.argument.type === 'Identifier')
return expr.argument.argument.name;
return null;
}
function collectExportedNames(estree) {
var _a, _b;
const names = [];
for (const node of estree.body) {
// Named exports: export const x, export function x, export { x }
if (node.type === 'ExportNamedDeclaration') {
const exportNode = node;
const decl = exportNode.declaration;
if (!decl) {
// Re-export specifiers: export { X } from 'module' or export { X }
for (const spec of exportNode.specifiers) {
// ES2022 allows Literal export names (e.g. export { foo as "bar" }),
// which have no .name — skip to avoid emitting `export const undefined = undefined`
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
if (spec.exported.type !== 'Identifier')
continue;
// 'default' is a reserved word and cannot be used as a variable name in a stub
if (spec.exported.name === 'default')
continue;
names.push({ name: spec.exported.name, isFunction: false });
}
continue;
}
if (decl.type === 'FunctionDeclaration' && decl.id) {
names.push({ name: decl.id.name, isFunction: true });
}
else if (decl.type === 'VariableDeclaration') {
for (const declarator of decl.declarations) {
if (declarator.id.type === 'Identifier') {
const isFn = ((_a = declarator.init) === null || _a === void 0 ? void 0 : _a.type) === 'ArrowFunctionExpression' ||
((_b = declarator.init) === null || _b === void 0 ? void 0 : _b.type) === 'FunctionExpression';
names.push({ name: declarator.id.name, isFunction: isFn });
}
else {
collectIdentifiersFromPattern(declarator.id, names);
}
}
}
}
// Default export: export default function Name() {} creates a named binding in scope
if (node.type === 'ExportDefaultDeclaration') {
const decl = node.declaration;
if ((decl.type === 'FunctionDeclaration' || decl.type === 'FunctionExpression') && decl.id) {
names.push({ name: decl.id.name, isFunction: true });
}
}
}
return names;
}
function buildStubEsmNode(names) {
const sourceLines = [];
const body = [];
for (const { name, isFunction } of names) {
if (isFunction) {
sourceLines.push(`export const ${name} = () => null;`);
body.push({
type: 'ExportNamedDeclaration',
declaration: {
type: 'VariableDeclaration',
declarations: [
{
type: 'VariableDeclarator',
id: { type: 'Identifier', name },
init: {
type: 'ArrowFunctionExpression',
params: [],
body: { type: 'Literal', value: null, raw: 'null' },
expression: true,
async: false,
generator: false,
},
},
],
kind: 'const',
},
specifiers: [],
source: null,
});
}
else {
sourceLines.push(`export const ${name} = undefined;`);
body.push({
type: 'ExportNamedDeclaration',
declaration: {
type: 'VariableDeclaration',
declarations: [
{
type: 'VariableDeclarator',
id: { type: 'Identifier', name },
init: { type: 'Identifier', name: 'undefined' },
},
],
kind: 'const',
},
specifiers: [],
source: null,
});
}
}
return {
type: 'mdxjsEsm',
value: sourceLines.join('\n'),
data: {
estree: {
type: 'Program',
body,
sourceType: 'module',
comments: [],
},
},
};
}
function isArrayOfStringLiterals(estree) {
const stmt = estree.body[0];
if (estree.body.length !== 1 || (stmt === null || stmt === void 0 ? void 0 : stmt.type) !== 'ExpressionStatement')
return false;
if (stmt.expression.type !== 'ArrayExpression')
return false;
return stmt.expression.elements.every((el) => (el === null || el === void 0 ? void 0 : el.type) === 'Literal' && typeof el.value === 'string');
}
export function remarkMdxRemoveJs() {
return (tree) => {
remove(tree, ['mdxTextExpression', 'mdxFlowExpression']);
// Collect exported names before stripping ESM so we can inject stubs.
// Map keyed by name deduplicates naturally.
const stubbedNamesMap = new Map();
visit(tree, (node) => {
var _a;
if (isMdxJsEsm(node) && ((_a = node.data) === null || _a === void 0 ? void 0 : _a.estree)) {
for (const entry of collectExportedNames(node.data.estree)) {
if (!stubbedNamesMap.has(entry.name))
stubbedNamesMap.set(entry.name, entry);
}
}
});
remove(tree, (node) => isMdxJsEsm(node));
// Sanitize JSX attributes. Any bare identifier that survives is added to stubbedNamesMap
// so it gets a stub even if its import was already stripped by a prior plugin.
visit(tree, (node) => {
if (!('attributes' in node))
return CONTINUE;
const newAttributes = node.attributes.map((attr) => {
var _a, _b, _c, _d, _e, _f, _g, _h;
if (attr.type === 'mdxJsxAttribute' && !(attr.value instanceof Object))
return attr;
if (typeof attr.value === 'string' && isStringSafe(attr.value))
return attr;
if (attr.type === 'mdxJsxAttribute' && attr.value instanceof Object) {
if (isStringSafe(attr.value.value)) {
const ident = extractIdentifierRef((_a = attr.value.data) === null || _a === void 0 ? void 0 : _a.estree);
if (ident && !stubbedNamesMap.has(ident))
stubbedNamesMap.set(ident, { name: ident, isFunction: false });
return attr;
}
if (((_b = attr.value.data) === null || _b === void 0 ? void 0 : _b.estree) && isArrayOfStringLiterals(attr.value.data.estree))
return attr;
if (isSafeStaticJsx((_c = attr.value.data) === null || _c === void 0 ? void 0 : _c.estree))
return attr;
if (attr.name === 'style' && ((_d = attr.value.data) === null || _d === void 0 ? void 0 : _d.estree)) {
const filteredEstree = filterStyleProperties(attr.value.data.estree);
if (filteredEstree) {
const stmt = filteredEstree.body[0];
if ((stmt === null || stmt === void 0 ? void 0 : stmt.type) === 'ExpressionStatement' &&
stmt.expression.type === 'ObjectExpression') {
attr.value.value = rebuildStyleValue(stmt.expression.properties);
attr.value.data.estree = filteredEstree;
return attr;
}
}
return undefined;
}
if (Object.keys(DEFAULT_PROP_EXPRESSIONS).includes(attr.name)) {
attr.value.value = (_f = (_e = DEFAULT_PROP_EXPRESSIONS[attr.name]) === null || _e === void 0 ? void 0 : _e.value) !== null && _f !== void 0 ? _f : '{}';
attr.value.data = {
estree: structuredClone((_h = (_g = DEFAULT_PROP_EXPRESSIONS[attr.name]) === null || _g === void 0 ? void 0 : _g.estree) !== null && _h !== void 0 ? _h : objectEstree),
};
return attr;
}
}
return undefined;
});
node.attributes = newAttributes.filter((attr) => attr !== undefined);
});
// Inject stubs after the attribute walk so identifiers collected from attributes are included
if (stubbedNamesMap.size > 0) {
tree.children.unshift(buildStubEsmNode([...stubbedNamesMap.values()]));
}
};
}