|
| 1 | +#!/usr/bin/env bash |
| 2 | +":"; //# comment; exec /usr/bin/env node --input-type=module - "$@" < "$0" |
| 3 | + |
| 4 | +import { readFile, writeFile } from "fs/promises"; |
| 5 | +import openapi from "openapi-typescript"; |
| 6 | +// import { resolve } from "path"; |
| 7 | +// import { cwd } from "process"; |
| 8 | + |
| 9 | +const NOTICE = `// This file was auto-generated by @insertish/oapi!\n`; |
| 10 | + |
| 11 | +readFile("OpenAPI.json").then((data) => { |
| 12 | + // Load and patch anything we need to |
| 13 | + let source = data.toString(); |
| 14 | +
|
| 15 | + if (process.env.REWRITE_ANYOF) { |
| 16 | + source = source.replace(/"anyOf"/g, '"oneOf"'); |
| 17 | + } |
| 18 | +
|
| 19 | + // Parse spec |
| 20 | + const spec = JSON.parse(source); |
| 21 | +
|
| 22 | + // Copy index.ts |
| 23 | + // readFile( |
| 24 | + // resolve(cwd(), "node_modules", "@insertish", "oapi", "src", "index.ts") |
| 25 | + // ).then((data) => writeFile("src/index.ts", data)); |
| 26 | + |
| 27 | + // Generate Schema |
| 28 | + openapi(spec).then((data) => writeFile("src/schema.ts", data)); |
| 29 | + |
| 30 | + // Route Types + Data |
| 31 | + { |
| 32 | + const entries = [ |
| 33 | + "import { paths } from './schema';", |
| 34 | + "export type APIRoutes =", |
| 35 | + ]; |
| 36 | + const paths = Object.keys(spec.paths); |
| 37 | + const queryData = {}; |
| 38 | + |
| 39 | + for (const path of paths) { |
| 40 | + const data = spec.paths[path]; |
| 41 | + const methods = Object.keys(data); |
| 42 | + |
| 43 | + let template = path.replace(/\{\w+\}/g, "${string}"); |
| 44 | + |
| 45 | + for (const method of methods) { |
| 46 | + const OPERATION = `paths['${path}']['${method}']`; |
| 47 | + |
| 48 | + const route = data[method]; |
| 49 | + const response = |
| 50 | + Object.keys(route["responses"]).find((x) => x !== "default") || |
| 51 | + "default"; |
| 52 | + const contentType = Object.keys( |
| 53 | + route["responses"][response]["content"] || {} |
| 54 | + )[0]; |
| 55 | + const RESPONSE = |
| 56 | + response === "204" || !contentType |
| 57 | + ? "undefined" |
| 58 | + : `${OPERATION}['responses']['${response}']['content']['${contentType}']`; |
| 59 | +
|
| 60 | + let queryParams = []; |
| 61 | + let hasBody = false; |
| 62 | +
|
| 63 | + if (route["parameters"]) { |
| 64 | + for (const parameter of route["parameters"]) { |
| 65 | + if (parameter.in === "query") { |
| 66 | + queryParams.push(parameter.name); |
| 67 | + } |
| 68 | + } |
| 69 | + } |
| 70 | +
|
| 71 | + if (route["requestBody"]?.["content"]?.["application/json"]) { |
| 72 | + hasBody = true; |
| 73 | + } |
| 74 | +
|
| 75 | + let params = "undefined"; |
| 76 | + if (hasBody || queryParams.length > 0) { |
| 77 | + let entries = []; |
| 78 | +
|
| 79 | + if (queryParams.length > 0) { |
| 80 | + entries.push(`${OPERATION}['parameters']['query']`); |
| 81 | + } |
| 82 | +
|
| 83 | + if (hasBody) { |
| 84 | + entries.push( |
| 85 | + `${OPERATION}['requestBody']['content']['application/json']` |
| 86 | + ); |
| 87 | + } |
| 88 | +
|
| 89 | + params = entries.join("|"); |
| 90 | + } |
| 91 | +
|
| 92 | + const parts = path.split("").filter((x) => x === "/").length; |
| 93 | + entries.push( |
| 94 | + `| { method: '${method}', path: \`${template}\`, parts: ${parts}, params: ${params}, response: ${RESPONSE} }` |
| 95 | + ); |
| 96 | +
|
| 97 | + if (/\{\w+\}/.test(path)) { |
| 98 | + entries.push( |
| 99 | + `| { method: '${method}', path: '-${path}', parts: ${parts}, params: ${params}, response: ${RESPONSE} }` |
| 100 | + ); |
| 101 | + } |
| 102 | +
|
| 103 | + queryData[path] = { |
| 104 | + ...queryData[path], |
| 105 | + [method]: queryParams, |
| 106 | + }; |
| 107 | + } |
| 108 | + } |
| 109 | +
|
| 110 | + const pathResolve = {}; |
| 111 | + for (const path of paths) { |
| 112 | + const segments = path.split("/"); |
| 113 | + segments.shift(); |
| 114 | + pathResolve[segments.length] = [ |
| 115 | + ...(pathResolve[segments.length] || []), |
| 116 | + segments.map((key) => (/\{.*\}/.test(key) ? [key] : key)), |
| 117 | + ]; |
| 118 | + } |
| 119 | +
|
| 120 | + writeFile("src/routes.ts", NOTICE + entries.join("\n") + ";"); |
| 121 | + writeFile( |
| 122 | + "src/params.ts", |
| 123 | + NOTICE + |
| 124 | + "export const pathResolve = " + |
| 125 | + JSON.stringify(pathResolve) + |
| 126 | + ";\n" + |
| 127 | + "export const queryParams = " + |
| 128 | + JSON.stringify(queryData) + |
| 129 | + ";" |
| 130 | + ); |
| 131 | + } |
| 132 | +
|
| 133 | + // Type Exports |
| 134 | + { |
| 135 | + const entries = ["import { components } from './schema';"]; |
| 136 | + const schemas = spec.components.schemas; |
| 137 | +
|
| 138 | + for (const schema of Object.keys(schemas)) { |
| 139 | + entries.push( |
| 140 | + `export type ${schema.replace( |
| 141 | + /\s/g, |
| 142 | + "_" |
| 143 | + )} = components['schemas']['${schema}'];` |
| 144 | + ); |
| 145 | + } |
| 146 | +
|
| 147 | + writeFile("src/types.ts", NOTICE + entries.join("\n") + ";"); |
| 148 | + } |
| 149 | +
|
| 150 | + // Default Base URL |
| 151 | + const baseURL = spec["servers"]?.[0]?.["url"]; |
| 152 | + writeFile( |
| 153 | + "src/baseURL.ts", |
| 154 | + NOTICE + |
| 155 | + `export const defaultBaseURL = ${ |
| 156 | + baseURL ? '"' + baseURL + '"' : "undefined" |
| 157 | + };` |
| 158 | + ); |
| 159 | +}); |
0 commit comments