Created
July 15, 2023 13:00
-
-
Save tschoffelen/c90267addffc3526e39e61c1deed3aee to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import fs from "node:fs/promises"; | |
import { createProcessor } from "@mdx-js/mdx"; | |
const pipeline = createProcessor(); | |
const flattenItems = (items) => { | |
return items.reduce((acc, item) => { | |
acc[item.name] = item.value; | |
return acc; | |
}, {}); | |
}; | |
const flattenDescription = (items) => { | |
return (items || []) | |
.reduce((acc, item) => { | |
if (item.type === "text") { | |
acc.push(item.value); | |
} else if (item.type === "inlineCode") { | |
acc.push(`\`${item.value}\``); | |
} else if (item.type === "code") { | |
acc.push(` \`\`\`${item.value}\`\`\``); | |
} else if (item.name === "Response" || item.name === "Parameter") { | |
// do nothing | |
} else if (item.children) { | |
acc.push(...flattenDescription(item.children)); | |
acc.push("\n"); | |
} else { | |
console.log("unknown type", item.type); | |
} | |
return acc; | |
}, []) | |
.join("") | |
.replace("\n", " "); | |
}; | |
const cleanType = (type) => { | |
if (type === "uuid" || type === "date") { | |
return "string"; | |
} | |
if (type === "string[]") { | |
return "array"; | |
} | |
return type.toLowerCase(); | |
}; | |
const payload = { | |
openapi: "3.0.0", | |
info: { | |
title: "My API", | |
version: "1.0.0", | |
}, | |
components: { | |
securitySchemes: { | |
bearerAuth: { | |
type: "http", | |
scheme: "bearer", | |
bearerFormat: "API key", | |
}, | |
}, | |
}, | |
security: [{ bearerAuth: [] }], | |
servers: [ | |
{ | |
url: "https://api.myapp.com", | |
description: "Production API", | |
}, | |
], | |
paths: {}, | |
}; | |
const processTree = (tree) => { | |
for (const child of tree.children) { | |
if (child.name === "ApiMethod") { | |
const description = []; | |
const endpoint = { | |
...flattenItems(child.attributes), | |
responses: {}, | |
requestBody: null, | |
parameters: [], | |
}; | |
for (const subchild of child.children) { | |
if (subchild.name === "Parameter") { | |
const param = flattenItems(subchild.attributes); | |
if (param.in === "body") { | |
if (!endpoint.requestBody) { | |
endpoint.requestBody = { | |
type: "object", | |
properties: {}, | |
required: [], | |
}; | |
} | |
if ( | |
"required" in param && | |
param.required !== false && | |
param.required !== "false" | |
) { | |
endpoint.requestBody.required.push(param.name); | |
} | |
const description = flattenDescription(subchild.children); | |
endpoint.requestBody.properties[param.name] = { | |
type: cleanType(param.type), | |
description, | |
}; | |
} else { | |
endpoint.parameters.push({ | |
name: param.name, | |
in: param.in, | |
description: flattenDescription(subchild.children), | |
required: | |
"required" in param && | |
param.required !== false && | |
param.required !== "false", | |
schema: { | |
type: cleanType(param.type), | |
}, | |
}); | |
} | |
} | |
if (subchild.name === "Response") { | |
const response = flattenItems(subchild.attributes); | |
let [status, description] = response.status.split(" "); | |
description = ( | |
description + | |
" " + | |
flattenDescription(subchild.children) | |
).trim(); | |
endpoint.responses[status.replace(":", "")] = { | |
description, | |
}; | |
} | |
} | |
if (!payload.paths[endpoint.path]) { | |
payload.paths[endpoint.path] = {}; | |
} | |
payload.paths[endpoint.path][endpoint.method] = { | |
summary: endpoint.summary, | |
description: flattenDescription(child.children), | |
responses: endpoint.responses, | |
parameters: endpoint.parameters, | |
requestBody: endpoint.requestBody | |
? { | |
content: { | |
"application/json": { | |
schema: endpoint.requestBody, | |
}, | |
}, | |
} | |
: undefined, | |
}; | |
continue; | |
} | |
if (child.children?.length) { | |
processTree(child); | |
} | |
} | |
}; | |
const loadTree = async (path) => { | |
const files = await fs.readdir(path); | |
for (const file of files) { | |
if (!file.includes(".")) { | |
await loadTree(`${path}/${file}`); | |
} | |
if (file.includes(".md")) { | |
const compiled = await pipeline.parse( | |
await fs.readFile(`${path}/${file}`) | |
); | |
processTree(compiled); | |
} | |
} | |
}; | |
await loadTree("./pages"); | |
await fs.mkdir("./public", { recursive: true }); | |
await fs.writeFile( | |
"./public/openapi.json", | |
JSON.stringify(payload, null, 2) | |
); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment