188 lines
5.9 KiB
TypeScript
188 lines
5.9 KiB
TypeScript
import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
import path from "node:path";
|
|
import yaml from "yaml";
|
|
import {
|
|
type GeneratedWebhookPage,
|
|
generateApiReference,
|
|
} from "../apiReferenceGenerator/index.js";
|
|
import { removeInternalFields } from "../openapiTransform/removeInternalFields.js";
|
|
import { transformNode } from "./transformNode.js";
|
|
|
|
export { stripJsDocTags } from "./stripJsDocTags.js";
|
|
export {
|
|
transformPythonCodeSample,
|
|
transformTypeScriptCodeSample,
|
|
} from "./transformCodeSamples.js";
|
|
export { resolveSchemaExample, transformNode } from "./transformNode.js";
|
|
|
|
/**
|
|
* Transforms an OpenAPI YAML document for Mintlify consumption.
|
|
*
|
|
* - Removes internal fields (marked with `internal: true` or `x-internal: true`)
|
|
* - Strips JSDoc tags from descriptions
|
|
* - Transforms Speakeasy code samples to use autumn-js format
|
|
* - Copies schema examples to response content level
|
|
*/
|
|
export function transformOpenApiForMintlify(yamlContent: string): string {
|
|
const doc = yaml.parse(yamlContent) as Record<string, unknown>;
|
|
const schemas = (doc.components as Record<string, unknown>)?.schemas as
|
|
| Record<string, unknown>
|
|
| undefined;
|
|
|
|
removeInternalFields({ openApiDocument: doc });
|
|
|
|
transformNode(doc, schemas);
|
|
return yaml.stringify(doc);
|
|
}
|
|
|
|
/**
|
|
* Generates Mintlify documentation from OpenAPI spec.
|
|
*
|
|
* 1. Transforms OpenAPI (strips JSDoc tags, fixes code samples)
|
|
* 2. Generates API reference MDX files with dynamic parameter fields
|
|
* 3. Updates docs.json navigation with webhook groups
|
|
*/
|
|
export async function generateMintlifyDocs({
|
|
openApiPath,
|
|
docsDir,
|
|
}: {
|
|
openApiPath: string;
|
|
docsDir: string;
|
|
}): Promise<void> {
|
|
console.log("Transforming OpenAPI for Mintlify docs...");
|
|
const yamlContent = readFileSync(openApiPath, "utf-8");
|
|
const transformedYaml = transformOpenApiForMintlify(yamlContent);
|
|
writeFileSync(openApiPath, transformedYaml);
|
|
console.log("Mintlify transformation complete");
|
|
|
|
console.log("Generating API reference MDX files...");
|
|
const manualMdxDir = path.resolve(docsDir, "../api-reference-generator");
|
|
const outputMdxDir = path.resolve(docsDir, "api-reference");
|
|
const { webhookPages } = await generateApiReference({
|
|
openApiPath,
|
|
manualMdxDir,
|
|
outputDir: outputMdxDir,
|
|
});
|
|
console.log("API reference MDX generation complete");
|
|
|
|
if (webhookPages.length > 0) {
|
|
updateDocsJsonWebhooks({ docsDir, webhookPages });
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Reads the Slack and Discord Svix transform source files from
|
|
* `others/svix-transforms/` and writes them as Mintlify snippet MDX files
|
|
* (fenced JS code blocks with a tab title) so they can be imported and
|
|
* rendered inside a `<CodeGroup>` on the docs page without needing to
|
|
* hand-copy the code.
|
|
*
|
|
* The transform code is the source of truth — running this keeps the
|
|
* docs page in sync automatically.
|
|
*/
|
|
export function syncSvixTransforms({
|
|
svixTransformsDir,
|
|
docsDir,
|
|
}: {
|
|
svixTransformsDir: string;
|
|
docsDir: string;
|
|
}): void {
|
|
console.log("Syncing Svix transforms to docs snippets...");
|
|
const snippetsDir = path.join(docsDir, "snippets", "svix-transforms");
|
|
mkdirSync(snippetsDir, { recursive: true });
|
|
|
|
const platforms = [
|
|
{ source: "slack.ts", snippet: "slack.mdx", title: "Slack" },
|
|
{ source: "discord.ts", snippet: "discord.mdx", title: "Discord" },
|
|
] as const;
|
|
|
|
for (const { source, snippet, title } of platforms) {
|
|
const sourcePath = path.join(svixTransformsDir, source);
|
|
const snippetPath = path.join(snippetsDir, snippet);
|
|
|
|
const code = readFileSync(sourcePath, "utf-8");
|
|
|
|
// A Mintlify snippet that renders as a fenced code block. The title
|
|
// after the language tag becomes the tab label inside <CodeGroup>.
|
|
const content = `{/* AUTO-GENERATED by syncSvixTransforms in packages/openapi/utils/mintlifyTransform/index.ts */}
|
|
{/* Source: others/svix-transforms/${source} */}
|
|
{/* Edit the source file, not this snippet. */}
|
|
|
|
\`\`\`js ${title}
|
|
${code.trimEnd()}
|
|
\`\`\`
|
|
`;
|
|
|
|
writeFileSync(snippetPath, content);
|
|
console.log(` Wrote ${path.relative(docsDir, snippetPath)}`);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Updates the docs.json navigation to include webhook groups in the
|
|
* API Reference tab, replacing any previously generated webhook groups.
|
|
*/
|
|
function updateDocsJsonWebhooks({
|
|
docsDir,
|
|
webhookPages,
|
|
}: {
|
|
docsDir: string;
|
|
webhookPages: GeneratedWebhookPage[];
|
|
}) {
|
|
const docsJsonPath = path.join(docsDir, "docs.json");
|
|
const docsJson = JSON.parse(readFileSync(docsJsonPath, "utf-8"));
|
|
|
|
const tabs = docsJson.navigation?.tabs;
|
|
if (!Array.isArray(tabs)) return;
|
|
|
|
const apiTab = tabs.find(
|
|
(tab: Record<string, unknown>) => tab.tab === "API Reference",
|
|
);
|
|
if (!apiTab?.groups || !Array.isArray(apiTab.groups)) return;
|
|
|
|
// Build nested subgroups under a single "Webhook Events" group
|
|
const groupedPages = new Map<string, string[]>();
|
|
for (const page of webhookPages) {
|
|
const existing = groupedPages.get(page.group) ?? [];
|
|
existing.push(page.pagePath);
|
|
groupedPages.set(page.group, existing);
|
|
}
|
|
|
|
const subgroups = [...groupedPages.entries()].map(
|
|
([groupName, pages]) => ({
|
|
group: groupName,
|
|
pages,
|
|
}),
|
|
);
|
|
|
|
const webhookEventsGroup = {
|
|
group: "Webhook Events",
|
|
pages: subgroups,
|
|
};
|
|
|
|
// Remove any existing "Webhook Events" or old "Webhooks:" groups
|
|
const filteredGroups = apiTab.groups.filter(
|
|
(group: Record<string, unknown>) => {
|
|
const name = group.group as string | undefined;
|
|
return name && !name.startsWith("Webhooks:") && name !== "Webhook Events";
|
|
},
|
|
);
|
|
|
|
// Insert before "Platform (Beta)" if it exists, otherwise at the end
|
|
const platformIdx = filteredGroups.findIndex(
|
|
(g: Record<string, unknown>) => g.group === "Platform (Beta)",
|
|
);
|
|
if (platformIdx >= 0) {
|
|
filteredGroups.splice(platformIdx, 0, webhookEventsGroup);
|
|
} else {
|
|
filteredGroups.push(webhookEventsGroup);
|
|
}
|
|
|
|
apiTab.groups = filteredGroups;
|
|
|
|
writeFileSync(docsJsonPath, `${JSON.stringify(docsJson, null, "\t")}\n`);
|
|
console.log(
|
|
` Updated docs.json with ${subgroups.length} webhook subgroup(s)`,
|
|
);
|
|
}
|