Files
2025-08-10 17:04:52 +10:00

158 lines
3.8 KiB
TypeScript

import * as ts from "typescript";
import type { Route, RouteMetadata } from ".";
import prettier from "prettier";
function encouragePrettierLinebreaks(rawType: string) {
return rawType.startsWith("{")
? rawType.slice(0, 1) + "\n" + rawType.slice(1)
: rawType;
}
async function renderType(metadata: RouteMetadata, type: ts.Type) {
const expandedType = metadata.checker.typeToTypeNode(
type,
metadata.sourceFile,
ts.NodeBuilderFlags.NoTruncation | ts.NodeBuilderFlags.InTypeAlias
);
const rawType = metadata.checker.typeToString(
type,
undefined,
ts.TypeFormatFlags.NoTruncation | ts.TypeFormatFlags.InTypeAlias
);
const newLinedType = encouragePrettierLinebreaks(rawType);
const pretty = await prettier.format(`type Body = ${newLinedType}`, {
parser: "typescript",
});
return pretty;
}
export default async function generateDocsPage(
route: Route,
metadata: RouteMetadata
) {
// ===== Body =====
let bodyText = "";
if (metadata.body) {
bodyText = `
## Request Body
${metadata.bodyComment ?? ""}
Request type: \`application/json\`. Body type (TypeScript definition):
`.trimStart();
bodyText += "```typescript\n";
bodyText += await renderType(metadata, metadata.body);
bodyText += "\n```";
} else if (metadata.bodyComment) {
bodyText = `
## Request Body
${metadata.bodyComment ?? ""}`;
}
// ===== Route Params =====
let paramTable = "";
if (metadata.routeParams) {
paramTable =
"## Route Parameters \n\n| Name | Type | About |\n| ---- | ---- | ---- |\n";
for (const [name, data] of Object.entries(metadata.routeParams)) {
paramTable += `| ${name} | ${data.type} | ${data.comment}`;
}
}
let queryTable = "";
if (metadata.query) {
const queryProperties = metadata.query.getProperties();
if (queryProperties.length > 0) {
queryTable =
"## Query Parameters\n\n| Name | Parser |\n| ---- | ---- |\n";
for (const property of queryProperties) {
queryTable += `| ${property.escapedName} | ${property.declarations?.at(0)?.getLastToken()?.getText()?.slice(1, -1) ?? "string"} |\n`;
}
}
}
// ===== ACLs =====
let routeAuthentication = "";
if (metadata.acls && metadata.acls.length > 0) {
routeAuthentication = `
Any of the following ACLs on a \`${metadata.aclMode}\` token are required to access this route:
${metadata.acls.map((e) => ` - \`${e}\``).join("\n")}
`.trimStart();
}
// If ACL is checked, but no specific ones are required
// Means authenticated, but only sessions
else if (metadata.acls) {
routeAuthentication = `Only \`${metadata.aclMode}\` **sessions** can access this route, not API tokens.`;
}
// ===== Client Warning =====
let clientWarning = "";
if (metadata.clientRoute) {
clientWarning =
"**This is a client route. It is not accessible through API tokens.**";
}
// ===== Response ======
let responseText = "";
if (metadata.response) {
responseText = `
## Response Body
${metadata.responseComment ?? ""}
${metadata.responseTag ? `This endpoint returns '${metadata.responseTag}'` : ""}
Response definition:
`.trimStart();
responseText += "```typescript\n";
responseText += await renderType(metadata, metadata.response);
responseText += "\n```";
} else if (metadata.responseComment) {
responseText = `
## Response Body
${metadata.responseComment}`.trim();
}
const deprecatedElement = `::deprecated\n::`;
const rawText = `
---
metaUrl: ${route.urlPath}
---
# ${route.method} \`${route.urlPath}\`
${metadata.deprecated ? deprecatedElement : ""}
## About this route
${metadata.routeDescription ?? "*No description provided.*"}
${routeAuthentication}
${clientWarning}
${queryTable}
${paramTable}
${bodyText}
${responseText}
`;
// Just for my sanity
const formatted = await prettier.format(rawText, { parser: "markdown" });
return formatted;
}