Files
izzy 0b9ccca95b chore: use bigint parse/stringify
chore: move cli generator into this repo
2025-10-22 17:47:18 +01:00

160 lines
4.3 KiB
JavaScript

#!/usr/bin/env bash
":"; //# comment; exec /usr/bin/env node --input-type=module - "$@" < "$0"
import { readFile, writeFile } from "fs/promises";
import openapi from "openapi-typescript";
// import { resolve } from "path";
// import { cwd } from "process";
const NOTICE = `// This file was auto-generated by @insertish/oapi!\n`;
readFile("OpenAPI.json").then((data) => {
// Load and patch anything we need to
let source = data.toString();
if (process.env.REWRITE_ANYOF) {
source = source.replace(/"anyOf"/g, '"oneOf"');
}
// Parse spec
const spec = JSON.parse(source);
// Copy index.ts
// readFile(
// resolve(cwd(), "node_modules", "@insertish", "oapi", "src", "index.ts")
// ).then((data) => writeFile("src/index.ts", data));
// Generate Schema
openapi(spec).then((data) => writeFile("src/schema.ts", data));
// Route Types + Data
{
const entries = [
"import { paths } from './schema';",
"export type APIRoutes =",
];
const paths = Object.keys(spec.paths);
const queryData = {};
for (const path of paths) {
const data = spec.paths[path];
const methods = Object.keys(data);
let template = path.replace(/\{\w+\}/g, "${string}");
for (const method of methods) {
const OPERATION = `paths['${path}']['${method}']`;
const route = data[method];
const response =
Object.keys(route["responses"]).find((x) => x !== "default") ||
"default";
const contentType = Object.keys(
route["responses"][response]["content"] || {}
)[0];
const RESPONSE =
response === "204" || !contentType
? "undefined"
: `${OPERATION}['responses']['${response}']['content']['${contentType}']`;
let queryParams = [];
let hasBody = false;
if (route["parameters"]) {
for (const parameter of route["parameters"]) {
if (parameter.in === "query") {
queryParams.push(parameter.name);
}
}
}
if (route["requestBody"]?.["content"]?.["application/json"]) {
hasBody = true;
}
let params = "undefined";
if (hasBody || queryParams.length > 0) {
let entries = [];
if (queryParams.length > 0) {
entries.push(`${OPERATION}['parameters']['query']`);
}
if (hasBody) {
entries.push(
`${OPERATION}['requestBody']['content']['application/json']`
);
}
params = entries.join("|");
}
const parts = path.split("").filter((x) => x === "/").length;
entries.push(
`| { method: '${method}', path: \`${template}\`, parts: ${parts}, params: ${params}, response: ${RESPONSE} }`
);
if (/\{\w+\}/.test(path)) {
entries.push(
`| { method: '${method}', path: '-${path}', parts: ${parts}, params: ${params}, response: ${RESPONSE} }`
);
}
queryData[path] = {
...queryData[path],
[method]: queryParams,
};
}
}
const pathResolve = {};
for (const path of paths) {
const segments = path.split("/");
segments.shift();
pathResolve[segments.length] = [
...(pathResolve[segments.length] || []),
segments.map((key) => (/\{.*\}/.test(key) ? [key] : key)),
];
}
writeFile("src/routes.ts", NOTICE + entries.join("\n") + ";");
writeFile(
"src/params.ts",
NOTICE +
"export const pathResolve = " +
JSON.stringify(pathResolve) +
";\n" +
"export const queryParams = " +
JSON.stringify(queryData) +
";"
);
}
// Type Exports
{
const entries = ["import { components } from './schema';"];
const schemas = spec.components.schemas;
for (const schema of Object.keys(schemas)) {
entries.push(
`export type ${schema.replace(
/\s/g,
"_"
)} = components['schemas']['${schema}'];`
);
}
writeFile("src/types.ts", NOTICE + entries.join("\n") + ";");
}
// Default Base URL
const baseURL = spec["servers"]?.[0]?.["url"];
writeFile(
"src/baseURL.ts",
NOTICE +
`export const defaultBaseURL = ${
baseURL ? '"' + baseURL + '"' : "undefined"
};`
);
});