typed-openapi
Version:
133 lines (117 loc) • 4.9 kB
text/typescript
import SwaggerParser from "@apidevtools/swagger-parser";
import type { OpenAPIObject } from "openapi3-ts/oas31";
import { basename, join, dirname, isAbsolute } from "pathe";
import { type } from "arktype";
import { mkdir, writeFile } from "fs/promises";
import {
allowedRuntimes,
generateFile,
DEFAULT_SUCCESS_STATUS_CODES,
DEFAULT_ERROR_STATUS_CODES,
type GeneratorOptions,
} from "./generator.ts";
import { mapOpenApiEndpoints } from "./map-openapi-endpoints.ts";
import { generateTanstackQueryFile } from "./tanstack-query.generator.ts";
import { prettify } from "./format.ts";
import type { NameTransformOptions } from "./types.ts";
import { generateDefaultFetcher } from "./default-fetcher.generator.ts";
const cwd = process.cwd();
const now = new Date();
async function ensureDir(dirPath: string): Promise<void> {
try {
await mkdir(dirPath, { recursive: true });
} catch (error) {
console.error(`Error ensuring directory: ${(error as Error).message}`);
}
}
export const optionsSchema = type({
"output?": "string",
runtime: allowedRuntimes,
tanstack: "boolean | string",
"defaultFetcher?": type({
"envApiBaseUrl?": "string",
"clientPath?": "string",
"fetcherName?": "string",
"apiName?": "string",
}),
schemasOnly: "boolean",
"includeClient?": "boolean | 'true' | 'false'",
"successStatusCodes?": "string",
"errorStatusCodes?": "string",
});
type GenerateClientFilesOptions = typeof optionsSchema.infer & {
nameTransform?: NameTransformOptions;
};
export async function generateClientFiles(input: string, options: GenerateClientFilesOptions) {
// TODO CLI option to save that file?
const openApiDoc = (await SwaggerParser.bundle(input)) as OpenAPIObject;
const ctx = mapOpenApiEndpoints(openApiDoc, options);
console.log(`Found ${ctx.endpointList.length} endpoints`);
// Parse success status codes if provided
const successStatusCodes = options.successStatusCodes
? (options.successStatusCodes.split(",").map((code) => parseInt(code.trim(), 10)) as readonly number[])
: undefined;
// Parse error status codes if provided
const errorStatusCodes = options.errorStatusCodes
? (options.errorStatusCodes.split(",").map((code) => parseInt(code.trim(), 10)) as readonly number[])
: undefined;
// Convert string boolean to actual boolean
const includeClient =
options.includeClient === "false" ? false : options.includeClient === "true" ? true : options.includeClient;
const generatorOptions: GeneratorOptions = {
...ctx,
runtime: options.runtime,
schemasOnly: options.schemasOnly,
nameTransform: options.nameTransform,
includeClient: includeClient ?? true,
successStatusCodes: successStatusCodes ?? DEFAULT_SUCCESS_STATUS_CODES,
errorStatusCodes: errorStatusCodes ?? DEFAULT_ERROR_STATUS_CODES,
};
const content = await prettify(generateFile(generatorOptions));
const outputPath = join(
cwd,
options.output ?? input + `.${options.runtime === "none" ? "client" : options.runtime}.ts`,
);
console.log("Generating client...", outputPath);
await ensureDir(dirname(outputPath));
await writeFile(outputPath, content);
if (options.tanstack) {
const tanstackContent = await generateTanstackQueryFile({
...generatorOptions,
relativeApiClientPath: "./" + basename(outputPath),
});
let tanstackOutputPath: string;
if (typeof options.tanstack === "string" && isAbsolute(options.tanstack)) {
tanstackOutputPath = options.tanstack;
} else {
tanstackOutputPath = join(
dirname(outputPath),
typeof options.tanstack === "string" ? options.tanstack : `tanstack.client.ts`,
);
}
console.log("Generating tanstack client...", tanstackOutputPath);
await ensureDir(dirname(tanstackOutputPath));
await writeFile(tanstackOutputPath, tanstackContent);
}
if (options.defaultFetcher) {
const defaultFetcherContent = generateDefaultFetcher({
envApiBaseUrl: options.defaultFetcher.envApiBaseUrl,
clientPath: options.defaultFetcher.clientPath ?? join(dirname(outputPath), basename(outputPath)),
fetcherName: options.defaultFetcher.fetcherName,
apiName: options.defaultFetcher.apiName,
});
let defaultFetcherOutputPath: string;
if (typeof options.defaultFetcher === "string" && isAbsolute(options.defaultFetcher)) {
defaultFetcherOutputPath = options.defaultFetcher;
} else {
defaultFetcherOutputPath = join(
dirname(outputPath),
typeof options.defaultFetcher === "string" ? options.defaultFetcher : `api.client.ts`,
);
}
console.log("Generating default fetcher...", defaultFetcherOutputPath);
await ensureDir(dirname(defaultFetcherOutputPath));
await writeFile(defaultFetcherOutputPath, defaultFetcherContent);
}
console.log(`Done in ${new Date().getTime() - now.getTime()}ms !`);
}