@omer-x/next-openapi-json-generator 1.0.1 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +394 -0
- package/dist/index.d.cts +11 -0
- package/dist/index.js +42 -4
- package/package.json +8 -3
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,394 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
|
|
30
|
+
// src/index.ts
|
|
31
|
+
var src_exports = {};
|
|
32
|
+
__export(src_exports, {
|
|
33
|
+
default: () => src_default
|
|
34
|
+
});
|
|
35
|
+
module.exports = __toCommonJS(src_exports);
|
|
36
|
+
|
|
37
|
+
// src/core/generateOpenApiSpec.ts
|
|
38
|
+
var import_package_metadata = __toESM(require("@omer-x/package-metadata"), 1);
|
|
39
|
+
|
|
40
|
+
// src/core/dir.ts
|
|
41
|
+
var import_fs = require("fs");
|
|
42
|
+
var import_promises = __toESM(require("fs/promises"), 1);
|
|
43
|
+
var import_node_path = __toESM(require("path"), 1);
|
|
44
|
+
var import_minimatch = require("minimatch");
|
|
45
|
+
async function directoryExists(dirPath) {
|
|
46
|
+
try {
|
|
47
|
+
await import_promises.default.access(dirPath, import_fs.constants.F_OK);
|
|
48
|
+
return true;
|
|
49
|
+
} catch (err) {
|
|
50
|
+
return false;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
async function getDirectoryItems(dirPath, targetFileName) {
|
|
54
|
+
const collection = [];
|
|
55
|
+
const files = await import_promises.default.readdir(dirPath);
|
|
56
|
+
for (const itemName of files) {
|
|
57
|
+
const itemPath = import_node_path.default.resolve(dirPath, itemName);
|
|
58
|
+
const stats = await import_promises.default.stat(itemPath);
|
|
59
|
+
if (stats.isDirectory()) {
|
|
60
|
+
const children = await getDirectoryItems(itemPath, targetFileName);
|
|
61
|
+
collection.push(...children);
|
|
62
|
+
} else if (itemName === targetFileName) {
|
|
63
|
+
collection.push(itemPath);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
return collection;
|
|
67
|
+
}
|
|
68
|
+
function filterDirectoryItems(rootPath, items, include, exclude) {
|
|
69
|
+
const includedPatterns = include.map((pattern) => new import_minimatch.Minimatch(pattern));
|
|
70
|
+
const excludedPatterns = exclude.map((pattern) => new import_minimatch.Minimatch(pattern));
|
|
71
|
+
return items.filter((item) => {
|
|
72
|
+
const relativePath = import_node_path.default.relative(rootPath, item);
|
|
73
|
+
const isIncluded = includedPatterns.some((pattern) => pattern.match(relativePath));
|
|
74
|
+
const isExcluded = excludedPatterns.some((pattern) => pattern.match(relativePath));
|
|
75
|
+
return (isIncluded || !include.length) && !isExcluded;
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// src/core/next.ts
|
|
80
|
+
var import_promises2 = __toESM(require("fs/promises"), 1);
|
|
81
|
+
var import_node_path2 = __toESM(require("path"), 1);
|
|
82
|
+
|
|
83
|
+
// src/core/middleware.ts
|
|
84
|
+
function detectMiddlewareName(code2) {
|
|
85
|
+
const match = code2.match(/middleware:\s*(\w+)/);
|
|
86
|
+
return match ? match[1] : null;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// src/core/transpile.ts
|
|
90
|
+
var import_typescript = require("typescript");
|
|
91
|
+
function removeImports(code2) {
|
|
92
|
+
return code2.replace(/^import\s.+\sfrom\s.+;?$/gm, "").trim();
|
|
93
|
+
}
|
|
94
|
+
function fixExports(code2) {
|
|
95
|
+
const validMethods = ["GET", "POST", "PUT", "PATCH", "DELETE"];
|
|
96
|
+
const exportFixer1 = validMethods.map((method) => `exports.${method} = void 0;
|
|
97
|
+
`);
|
|
98
|
+
const exportFixer2 = `module.exports = { ${validMethods.map((m) => `${m}: exports.${m}`).join(", ")} }`;
|
|
99
|
+
return `${exportFixer1}
|
|
100
|
+
${code2}
|
|
101
|
+
${exportFixer2}`;
|
|
102
|
+
}
|
|
103
|
+
function injectMiddlewareFixer(middlewareName) {
|
|
104
|
+
return `const ${middlewareName} = (handler) => handler;`;
|
|
105
|
+
}
|
|
106
|
+
function transpile(rawCode, routeDefinerName, middlewareName) {
|
|
107
|
+
const code2 = fixExports(removeImports(rawCode));
|
|
108
|
+
const parts = [
|
|
109
|
+
`import ${routeDefinerName} from '@omer-x/next-openapi-route-handler';`,
|
|
110
|
+
"import z from 'zod';",
|
|
111
|
+
middlewareName ? injectMiddlewareFixer(middlewareName) : "",
|
|
112
|
+
code2
|
|
113
|
+
];
|
|
114
|
+
return (0, import_typescript.transpile)(parts.join("\n"));
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// src/core/next.ts
|
|
118
|
+
async function findAppFolderPath() {
|
|
119
|
+
const inSrc = import_node_path2.default.resolve(process.cwd(), "src", "app");
|
|
120
|
+
if (await directoryExists(inSrc)) {
|
|
121
|
+
return inSrc;
|
|
122
|
+
}
|
|
123
|
+
const inRoot = import_node_path2.default.resolve(process.cwd(), "app");
|
|
124
|
+
if (await directoryExists(inRoot)) {
|
|
125
|
+
return inRoot;
|
|
126
|
+
}
|
|
127
|
+
return null;
|
|
128
|
+
}
|
|
129
|
+
function injectSchemas(code2, refName) {
|
|
130
|
+
return code2.replace(new RegExp(`\\b${refName}\\.`, "g"), `global.schemas[${refName}].`).replace(new RegExp(`\\b${refName}\\b`, "g"), `"${refName}"`);
|
|
131
|
+
}
|
|
132
|
+
function safeEval(code, routePath) {
|
|
133
|
+
try {
|
|
134
|
+
return eval(code);
|
|
135
|
+
} catch (error) {
|
|
136
|
+
console.log(`An error occured while evaluating the route exports from "${routePath}"`);
|
|
137
|
+
throw error;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
async function getRouteExports(routePath2, routeDefinerName, schemas) {
|
|
141
|
+
const rawCode = await import_promises2.default.readFile(routePath2, "utf-8");
|
|
142
|
+
const middlewareName = detectMiddlewareName(rawCode);
|
|
143
|
+
const code2 = transpile(rawCode, routeDefinerName, middlewareName);
|
|
144
|
+
const fixedCode = Object.keys(schemas).reduce(injectSchemas, code2);
|
|
145
|
+
global.schemas = schemas;
|
|
146
|
+
if (middlewareName) {
|
|
147
|
+
}
|
|
148
|
+
const result = safeEval(fixedCode, routePath2);
|
|
149
|
+
delete global.schemas;
|
|
150
|
+
if (middlewareName) {
|
|
151
|
+
}
|
|
152
|
+
return result;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// src/core/options.ts
|
|
156
|
+
function verifyOptions(include, exclude) {
|
|
157
|
+
if (process.env.NODE_ENV === "development") {
|
|
158
|
+
for (const item of include) {
|
|
159
|
+
if (!item.endsWith("/route.ts")) {
|
|
160
|
+
console.log(`${item} is not a valid route handler path`);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
for (const item of exclude) {
|
|
164
|
+
if (!item.endsWith("/route.ts")) {
|
|
165
|
+
console.log(`${item} is not a valid route handler path`);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
return {
|
|
170
|
+
include: include.filter((item) => item.endsWith("/route.ts")),
|
|
171
|
+
exclude: exclude.filter((item) => item.endsWith("/route.ts"))
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// src/utils/deepEqual.ts
|
|
176
|
+
function deepEqual(a, b) {
|
|
177
|
+
if (typeof a !== typeof b) return false;
|
|
178
|
+
switch (typeof a) {
|
|
179
|
+
case "object": {
|
|
180
|
+
if (!a || !b) return a === b;
|
|
181
|
+
if (Array.isArray(a) && Array.isArray(b)) {
|
|
182
|
+
return a.every((item, index) => deepEqual(item, b[index]));
|
|
183
|
+
}
|
|
184
|
+
if (Object.keys(a).length !== Object.keys(b).length) return false;
|
|
185
|
+
return Object.entries(a).every(([key, value]) => {
|
|
186
|
+
return deepEqual(value, b[key]);
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
case "function":
|
|
190
|
+
case "symbol":
|
|
191
|
+
return false;
|
|
192
|
+
default:
|
|
193
|
+
return a === b;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// src/core/zod-to-openapi.ts
|
|
198
|
+
var import_zod_to_json_schema = __toESM(require("zod-to-json-schema"), 1);
|
|
199
|
+
|
|
200
|
+
// src/utils/zod-schema.ts
|
|
201
|
+
function isFile(schema) {
|
|
202
|
+
const result = schema.safeParse(new File([], "nothing.txt"));
|
|
203
|
+
return result.success;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// src/core/zod-to-openapi.ts
|
|
207
|
+
function convertToOpenAPI(schema, isArray) {
|
|
208
|
+
const result = (0, import_zod_to_json_schema.default)(isArray ? schema.array() : schema, {
|
|
209
|
+
target: "openApi3",
|
|
210
|
+
$refStrategy: "none"
|
|
211
|
+
});
|
|
212
|
+
if (result.type === "object" && result.properties) {
|
|
213
|
+
for (const [propName, prop] of Object.entries(schema.shape)) {
|
|
214
|
+
if (isFile(prop)) {
|
|
215
|
+
result.properties[propName] = {
|
|
216
|
+
type: "string",
|
|
217
|
+
format: "binary",
|
|
218
|
+
description: prop.description
|
|
219
|
+
// contentEncoding: "base64", // swagger-ui-react doesn't support this
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
return result;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// src/core/mask.ts
|
|
228
|
+
function maskWithReference(schema, storedSchemas, self) {
|
|
229
|
+
if (self) {
|
|
230
|
+
for (const [schemaName, zodSchema] of Object.entries(storedSchemas)) {
|
|
231
|
+
if (deepEqual(schema, convertToOpenAPI(zodSchema, false))) {
|
|
232
|
+
return {
|
|
233
|
+
$ref: `#/components/schemas/${schemaName}`
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
if ("$ref" in schema) return schema;
|
|
239
|
+
if (schema.oneOf) {
|
|
240
|
+
return {
|
|
241
|
+
...schema,
|
|
242
|
+
oneOf: schema.oneOf.map((i) => maskWithReference(i, storedSchemas, true))
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
if (schema.anyOf) {
|
|
246
|
+
return {
|
|
247
|
+
...schema,
|
|
248
|
+
anyOf: schema.anyOf.map((i) => maskWithReference(i, storedSchemas, true))
|
|
249
|
+
};
|
|
250
|
+
}
|
|
251
|
+
switch (schema.type) {
|
|
252
|
+
case "object":
|
|
253
|
+
return {
|
|
254
|
+
...schema,
|
|
255
|
+
properties: Object.entries(schema.properties ?? {}).reduce((props, [propName, prop]) => ({
|
|
256
|
+
...props,
|
|
257
|
+
[propName]: maskWithReference(prop, storedSchemas, true)
|
|
258
|
+
}), {})
|
|
259
|
+
};
|
|
260
|
+
case "array":
|
|
261
|
+
if (Array.isArray(schema.items)) {
|
|
262
|
+
return {
|
|
263
|
+
...schema,
|
|
264
|
+
items: schema.items.map((i) => maskWithReference(i, storedSchemas, true))
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
return {
|
|
268
|
+
...schema,
|
|
269
|
+
items: maskWithReference(schema.items, storedSchemas, true)
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
return schema;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
// src/core/operation-mask.ts
|
|
276
|
+
function maskSchema(storedSchemas, schema) {
|
|
277
|
+
if (!schema) return schema;
|
|
278
|
+
return maskWithReference(schema, storedSchemas, true);
|
|
279
|
+
}
|
|
280
|
+
function maskParameterSchema(param, storedSchemas) {
|
|
281
|
+
if ("$ref" in param) return param;
|
|
282
|
+
return { ...param, schema: maskSchema(storedSchemas, param.schema) };
|
|
283
|
+
}
|
|
284
|
+
function maskContentSchema(storedSchemas, bodyContent) {
|
|
285
|
+
if (!bodyContent) return bodyContent;
|
|
286
|
+
return Object.entries(bodyContent).reduce((collection, [contentType, content]) => ({
|
|
287
|
+
...collection,
|
|
288
|
+
[contentType]: {
|
|
289
|
+
...content,
|
|
290
|
+
schema: maskSchema(storedSchemas, content.schema)
|
|
291
|
+
}
|
|
292
|
+
}), {});
|
|
293
|
+
}
|
|
294
|
+
function maskRequestBodySchema(storedSchemas, body) {
|
|
295
|
+
if (!body || "$ref" in body) return body;
|
|
296
|
+
return { ...body, content: maskContentSchema(storedSchemas, body.content) };
|
|
297
|
+
}
|
|
298
|
+
function maskResponseSchema(storedSchemas, response) {
|
|
299
|
+
if ("$ref" in response) return response;
|
|
300
|
+
return { ...response, content: maskContentSchema(storedSchemas, response.content) };
|
|
301
|
+
}
|
|
302
|
+
function maskSchemasInResponses(storedSchemas, responses) {
|
|
303
|
+
if (!responses) return responses;
|
|
304
|
+
return Object.entries(responses).reduce((collection, [key, response]) => ({
|
|
305
|
+
...collection,
|
|
306
|
+
[key]: maskResponseSchema(storedSchemas, response)
|
|
307
|
+
}), {});
|
|
308
|
+
}
|
|
309
|
+
function maskOperationSchemas(operation, storedSchemas) {
|
|
310
|
+
return {
|
|
311
|
+
...operation,
|
|
312
|
+
parameters: operation.parameters?.map((p) => maskParameterSchema(p, storedSchemas)),
|
|
313
|
+
requestBody: maskRequestBodySchema(storedSchemas, operation.requestBody),
|
|
314
|
+
responses: maskSchemasInResponses(storedSchemas, operation.responses)
|
|
315
|
+
};
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
// src/core/route.ts
|
|
319
|
+
function getRoutePathName(filePath, rootPath) {
|
|
320
|
+
return filePath.replace(rootPath, "").replace("[", "{").replace("]", "}").replaceAll("\\", "/").replace("/route.ts", "");
|
|
321
|
+
}
|
|
322
|
+
function createRouteRecord(method, filePath, rootPath, apiData) {
|
|
323
|
+
return {
|
|
324
|
+
method: method.toLocaleLowerCase(),
|
|
325
|
+
path: getRoutePathName(filePath, rootPath),
|
|
326
|
+
apiData
|
|
327
|
+
};
|
|
328
|
+
}
|
|
329
|
+
function bundlePaths(source, storedSchemas) {
|
|
330
|
+
source.sort((a, b) => a.path.localeCompare(b.path));
|
|
331
|
+
return source.reduce((collection, route) => ({
|
|
332
|
+
...collection,
|
|
333
|
+
[route.path]: {
|
|
334
|
+
...collection[route.path],
|
|
335
|
+
[route.method]: maskOperationSchemas(route.apiData, storedSchemas)
|
|
336
|
+
}
|
|
337
|
+
}), {});
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
// src/core/schema.ts
|
|
341
|
+
function bundleSchemas(schemas) {
|
|
342
|
+
const bundledSchemas = Object.keys(schemas).reduce((collection, schemaName) => {
|
|
343
|
+
return {
|
|
344
|
+
...collection,
|
|
345
|
+
[schemaName]: convertToOpenAPI(schemas[schemaName], false)
|
|
346
|
+
};
|
|
347
|
+
}, {});
|
|
348
|
+
return Object.entries(bundledSchemas).reduce((bundle, [schemaName, schema]) => ({
|
|
349
|
+
...bundle,
|
|
350
|
+
[schemaName]: maskWithReference(schema, schemas, false)
|
|
351
|
+
}), {});
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
// src/core/generateOpenApiSpec.ts
|
|
355
|
+
async function generateOpenApiSpec(schemas, {
|
|
356
|
+
include: includeOption = [],
|
|
357
|
+
exclude: excludeOption = [],
|
|
358
|
+
routeDefinerName = "defineRoute"
|
|
359
|
+
} = {}) {
|
|
360
|
+
const verifiedOptions = verifyOptions(includeOption, excludeOption);
|
|
361
|
+
const appFolderPath = await findAppFolderPath();
|
|
362
|
+
if (!appFolderPath) throw new Error("This is not a Next.js application!");
|
|
363
|
+
const routes = await getDirectoryItems(appFolderPath, "route.ts");
|
|
364
|
+
const verifiedRoutes = filterDirectoryItems(appFolderPath, routes, verifiedOptions.include, verifiedOptions.exclude);
|
|
365
|
+
const validRoutes = [];
|
|
366
|
+
for (const route of verifiedRoutes) {
|
|
367
|
+
const exportedRouteHandlers = await getRouteExports(route, routeDefinerName, schemas);
|
|
368
|
+
for (const [method, routeHandler] of Object.entries(exportedRouteHandlers)) {
|
|
369
|
+
if (!routeHandler || !routeHandler.apiData) continue;
|
|
370
|
+
validRoutes.push(createRouteRecord(
|
|
371
|
+
method.toLocaleLowerCase(),
|
|
372
|
+
route,
|
|
373
|
+
appFolderPath,
|
|
374
|
+
routeHandler.apiData
|
|
375
|
+
));
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
const metadata = (0, import_package_metadata.default)();
|
|
379
|
+
return {
|
|
380
|
+
openapi: "3.1.0",
|
|
381
|
+
info: {
|
|
382
|
+
title: metadata.serviceName,
|
|
383
|
+
version: metadata.version
|
|
384
|
+
},
|
|
385
|
+
paths: bundlePaths(validRoutes, schemas),
|
|
386
|
+
components: {
|
|
387
|
+
schemas: bundleSchemas(schemas)
|
|
388
|
+
},
|
|
389
|
+
tags: []
|
|
390
|
+
};
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
// src/index.ts
|
|
394
|
+
var src_default = generateOpenApiSpec;
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { OpenApiDocument } from '@omer-x/openapi-types';
|
|
2
|
+
import { ZodType } from 'zod';
|
|
3
|
+
|
|
4
|
+
type GeneratorOptions = {
|
|
5
|
+
include?: string[];
|
|
6
|
+
exclude?: string[];
|
|
7
|
+
routeDefinerName?: string;
|
|
8
|
+
};
|
|
9
|
+
declare function generateOpenApiSpec(schemas: Record<string, ZodType>, { include: includeOption, exclude: excludeOption, routeDefinerName, }?: GeneratorOptions): Promise<Omit<OpenApiDocument, "components"> & Required<Pick<OpenApiDocument, "components">>>;
|
|
10
|
+
|
|
11
|
+
export { generateOpenApiSpec as default };
|
package/dist/index.js
CHANGED
|
@@ -44,6 +44,12 @@ function filterDirectoryItems(rootPath, items, include, exclude) {
|
|
|
44
44
|
import fs2 from "node:fs/promises";
|
|
45
45
|
import path2 from "node:path";
|
|
46
46
|
|
|
47
|
+
// src/core/middleware.ts
|
|
48
|
+
function detectMiddlewareName(code2) {
|
|
49
|
+
const match = code2.match(/middleware:\s*(\w+)/);
|
|
50
|
+
return match ? match[1] : null;
|
|
51
|
+
}
|
|
52
|
+
|
|
47
53
|
// src/core/transpile.ts
|
|
48
54
|
import { transpile as tsTranspile } from "typescript";
|
|
49
55
|
function removeImports(code2) {
|
|
@@ -58,11 +64,15 @@ function fixExports(code2) {
|
|
|
58
64
|
${code2}
|
|
59
65
|
${exportFixer2}`;
|
|
60
66
|
}
|
|
61
|
-
function
|
|
67
|
+
function injectMiddlewareFixer(middlewareName) {
|
|
68
|
+
return `const ${middlewareName} = (handler) => handler;`;
|
|
69
|
+
}
|
|
70
|
+
function transpile(rawCode, routeDefinerName, middlewareName) {
|
|
62
71
|
const code2 = fixExports(removeImports(rawCode));
|
|
63
72
|
const parts = [
|
|
64
73
|
`import ${routeDefinerName} from '@omer-x/next-openapi-route-handler';`,
|
|
65
74
|
"import z from 'zod';",
|
|
75
|
+
middlewareName ? injectMiddlewareFixer(middlewareName) : "",
|
|
66
76
|
code2
|
|
67
77
|
];
|
|
68
78
|
return tsTranspile(parts.join("\n"));
|
|
@@ -92,12 +102,17 @@ function safeEval(code, routePath) {
|
|
|
92
102
|
}
|
|
93
103
|
}
|
|
94
104
|
async function getRouteExports(routePath2, routeDefinerName, schemas) {
|
|
95
|
-
const
|
|
96
|
-
const
|
|
105
|
+
const rawCode = await fs2.readFile(routePath2, "utf-8");
|
|
106
|
+
const middlewareName = detectMiddlewareName(rawCode);
|
|
107
|
+
const code2 = transpile(rawCode, routeDefinerName, middlewareName);
|
|
97
108
|
const fixedCode = Object.keys(schemas).reduce(injectSchemas, code2);
|
|
98
109
|
global.schemas = schemas;
|
|
110
|
+
if (middlewareName) {
|
|
111
|
+
}
|
|
99
112
|
const result = safeEval(fixedCode, routePath2);
|
|
100
113
|
delete global.schemas;
|
|
114
|
+
if (middlewareName) {
|
|
115
|
+
}
|
|
101
116
|
return result;
|
|
102
117
|
}
|
|
103
118
|
|
|
@@ -130,7 +145,10 @@ function deepEqual(a, b) {
|
|
|
130
145
|
if (Array.isArray(a) && Array.isArray(b)) {
|
|
131
146
|
return a.every((item, index) => deepEqual(item, b[index]));
|
|
132
147
|
}
|
|
133
|
-
|
|
148
|
+
if (Object.keys(a).length !== Object.keys(b).length) return false;
|
|
149
|
+
return Object.entries(a).every(([key, value]) => {
|
|
150
|
+
return deepEqual(value, b[key]);
|
|
151
|
+
});
|
|
134
152
|
}
|
|
135
153
|
case "function":
|
|
136
154
|
case "symbol":
|
|
@@ -142,11 +160,31 @@ function deepEqual(a, b) {
|
|
|
142
160
|
|
|
143
161
|
// src/core/zod-to-openapi.ts
|
|
144
162
|
import zodToJsonSchema from "zod-to-json-schema";
|
|
163
|
+
|
|
164
|
+
// src/utils/zod-schema.ts
|
|
165
|
+
function isFile(schema) {
|
|
166
|
+
const result = schema.safeParse(new File([], "nothing.txt"));
|
|
167
|
+
return result.success;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// src/core/zod-to-openapi.ts
|
|
145
171
|
function convertToOpenAPI(schema, isArray) {
|
|
146
172
|
const result = zodToJsonSchema(isArray ? schema.array() : schema, {
|
|
147
173
|
target: "openApi3",
|
|
148
174
|
$refStrategy: "none"
|
|
149
175
|
});
|
|
176
|
+
if (result.type === "object" && result.properties) {
|
|
177
|
+
for (const [propName, prop] of Object.entries(schema.shape)) {
|
|
178
|
+
if (isFile(prop)) {
|
|
179
|
+
result.properties[propName] = {
|
|
180
|
+
type: "string",
|
|
181
|
+
format: "binary",
|
|
182
|
+
description: prop.description
|
|
183
|
+
// contentEncoding: "base64", // swagger-ui-react doesn't support this
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
150
188
|
return result;
|
|
151
189
|
}
|
|
152
190
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@omer-x/next-openapi-json-generator",
|
|
3
|
-
"version": "1.0
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"description": "a Next.js plugin to generate OpenAPI documentation from route handlers",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"next.js",
|
|
@@ -29,11 +29,16 @@
|
|
|
29
29
|
},
|
|
30
30
|
"license": "MIT",
|
|
31
31
|
"type": "module",
|
|
32
|
-
"main": "dist/index.js",
|
|
33
|
-
"types": "dist/index.d.ts",
|
|
34
32
|
"files": [
|
|
35
33
|
"dist/"
|
|
36
34
|
],
|
|
35
|
+
"exports": {
|
|
36
|
+
".": {
|
|
37
|
+
"types": "./dist/index.d.ts",
|
|
38
|
+
"import": "./dist/index.js",
|
|
39
|
+
"require": "./dist/index.cjs"
|
|
40
|
+
}
|
|
41
|
+
},
|
|
37
42
|
"scripts": {
|
|
38
43
|
"test": "jest",
|
|
39
44
|
"dev": "tsup --watch",
|