@omer-x/next-openapi-json-generator 1.1.1 → 1.2.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 +73 -14
- package/dist/index.d.cts +7 -1
- package/dist/index.d.ts +7 -1
- package/dist/index.js +73 -14
- package/package.json +9 -9
package/dist/index.cjs
CHANGED
|
@@ -35,8 +35,39 @@ __export(src_exports, {
|
|
|
35
35
|
module.exports = __toCommonJS(src_exports);
|
|
36
36
|
|
|
37
37
|
// src/core/generateOpenApiSpec.ts
|
|
38
|
+
var import_node_path4 = __toESM(require("path"), 1);
|
|
38
39
|
var import_package_metadata = __toESM(require("@omer-x/package-metadata"), 1);
|
|
39
40
|
|
|
41
|
+
// src/utils/object.ts
|
|
42
|
+
function omit(object, ...keys) {
|
|
43
|
+
return Object.fromEntries(Object.entries(object).filter(([key]) => !keys.includes(key)));
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// src/core/clearUnusedSchemas.ts
|
|
47
|
+
function countReferences(schemaName, source) {
|
|
48
|
+
return (source.match(new RegExp(`"#/components/schemas/${schemaName}"`, "g")) ?? []).length;
|
|
49
|
+
}
|
|
50
|
+
function clearUnusedSchemas({
|
|
51
|
+
paths,
|
|
52
|
+
components
|
|
53
|
+
}) {
|
|
54
|
+
if (!components.schemas) return { paths, components };
|
|
55
|
+
const stringifiedPaths = JSON.stringify(paths);
|
|
56
|
+
const stringifiedSchemas = Object.fromEntries(Object.entries(components.schemas).map(([schemaName, schema]) => {
|
|
57
|
+
return [schemaName, JSON.stringify(schema)];
|
|
58
|
+
}));
|
|
59
|
+
return {
|
|
60
|
+
paths,
|
|
61
|
+
components: {
|
|
62
|
+
...components,
|
|
63
|
+
schemas: Object.fromEntries(Object.entries(components.schemas).filter(([schemaName]) => {
|
|
64
|
+
const otherSchemas = omit(stringifiedSchemas, schemaName);
|
|
65
|
+
return countReferences(schemaName, stringifiedPaths) > 0 || countReferences(schemaName, Object.values(otherSchemas).join("")) > 0;
|
|
66
|
+
}))
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
|
|
40
71
|
// src/core/dir.ts
|
|
41
72
|
var import_fs = require("fs");
|
|
42
73
|
var import_promises = __toESM(require("fs/promises"), 1);
|
|
@@ -46,7 +77,7 @@ async function directoryExists(dirPath) {
|
|
|
46
77
|
try {
|
|
47
78
|
await import_promises.default.access(dirPath, import_fs.constants.F_OK);
|
|
48
79
|
return true;
|
|
49
|
-
} catch
|
|
80
|
+
} catch {
|
|
50
81
|
return false;
|
|
51
82
|
}
|
|
52
83
|
}
|
|
@@ -76,8 +107,19 @@ function filterDirectoryItems(rootPath, items, include, exclude) {
|
|
|
76
107
|
});
|
|
77
108
|
}
|
|
78
109
|
|
|
79
|
-
// src/core/
|
|
110
|
+
// src/core/isDocumentedRoute.ts
|
|
80
111
|
var import_promises2 = __toESM(require("fs/promises"), 1);
|
|
112
|
+
async function isDocumentedRoute(routePath2) {
|
|
113
|
+
try {
|
|
114
|
+
const rawCode = await import_promises2.default.readFile(routePath2, "utf-8");
|
|
115
|
+
return rawCode.includes("@omer-x/next-openapi-route-handler");
|
|
116
|
+
} catch {
|
|
117
|
+
return false;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// src/core/next.ts
|
|
122
|
+
var import_promises3 = __toESM(require("fs/promises"), 1);
|
|
81
123
|
var import_node_path2 = __toESM(require("path"), 1);
|
|
82
124
|
|
|
83
125
|
// src/core/middleware.ts
|
|
@@ -138,7 +180,7 @@ function safeEval(code, routePath) {
|
|
|
138
180
|
}
|
|
139
181
|
}
|
|
140
182
|
async function getRouteExports(routePath2, routeDefinerName, schemas) {
|
|
141
|
-
const rawCode = await
|
|
183
|
+
const rawCode = await import_promises3.default.readFile(routePath2, "utf-8");
|
|
142
184
|
const middlewareName = detectMiddlewareName(rawCode);
|
|
143
185
|
const code2 = transpile(rawCode, routeDefinerName, middlewareName);
|
|
144
186
|
const fixedCode = Object.keys(schemas).reduce(injectSchemas, code2);
|
|
@@ -172,6 +214,13 @@ function verifyOptions(include, exclude) {
|
|
|
172
214
|
};
|
|
173
215
|
}
|
|
174
216
|
|
|
217
|
+
// src/core/getRoutePathName.ts
|
|
218
|
+
var import_node_path3 = __toESM(require("path"), 1);
|
|
219
|
+
function getRoutePathName(filePath, rootPath) {
|
|
220
|
+
const dirName = import_node_path3.default.dirname(filePath);
|
|
221
|
+
return "/" + import_node_path3.default.relative(rootPath, dirName).replaceAll("[", "{").replaceAll("]", "}").replaceAll("\\", "/");
|
|
222
|
+
}
|
|
223
|
+
|
|
175
224
|
// src/utils/deepEqual.ts
|
|
176
225
|
function deepEqual(a, b) {
|
|
177
226
|
if (typeof a !== typeof b) return false;
|
|
@@ -316,9 +365,6 @@ function maskOperationSchemas(operation, storedSchemas) {
|
|
|
316
365
|
}
|
|
317
366
|
|
|
318
367
|
// src/core/route.ts
|
|
319
|
-
function getRoutePathName(filePath, rootPath) {
|
|
320
|
-
return filePath.replace(rootPath, "").replace("[", "{").replace("]", "}").replaceAll("\\", "/").replace("/route.ts", "");
|
|
321
|
-
}
|
|
322
368
|
function createRouteRecord(method, filePath, rootPath, apiData) {
|
|
323
369
|
return {
|
|
324
370
|
method: method.toLocaleLowerCase(),
|
|
@@ -355,37 +401,50 @@ function bundleSchemas(schemas) {
|
|
|
355
401
|
async function generateOpenApiSpec(schemas, {
|
|
356
402
|
include: includeOption = [],
|
|
357
403
|
exclude: excludeOption = [],
|
|
358
|
-
routeDefinerName = "defineRoute"
|
|
404
|
+
routeDefinerName = "defineRoute",
|
|
405
|
+
rootPath: additionalRootPath,
|
|
406
|
+
servers,
|
|
407
|
+
security,
|
|
408
|
+
securitySchemes
|
|
359
409
|
} = {}) {
|
|
360
410
|
const verifiedOptions = verifyOptions(includeOption, excludeOption);
|
|
361
411
|
const appFolderPath = await findAppFolderPath();
|
|
362
412
|
if (!appFolderPath) throw new Error("This is not a Next.js application!");
|
|
363
|
-
const
|
|
364
|
-
const
|
|
413
|
+
const rootPath = additionalRootPath ? import_node_path4.default.resolve(appFolderPath, "./" + additionalRootPath) : appFolderPath;
|
|
414
|
+
const routes = await getDirectoryItems(rootPath, "route.ts");
|
|
415
|
+
const verifiedRoutes = filterDirectoryItems(rootPath, routes, verifiedOptions.include, verifiedOptions.exclude);
|
|
365
416
|
const validRoutes = [];
|
|
366
417
|
for (const route of verifiedRoutes) {
|
|
418
|
+
const isDocumented = await isDocumentedRoute(route);
|
|
419
|
+
if (!isDocumented) continue;
|
|
367
420
|
const exportedRouteHandlers = await getRouteExports(route, routeDefinerName, schemas);
|
|
368
421
|
for (const [method, routeHandler] of Object.entries(exportedRouteHandlers)) {
|
|
369
422
|
if (!routeHandler || !routeHandler.apiData) continue;
|
|
370
423
|
validRoutes.push(createRouteRecord(
|
|
371
424
|
method.toLocaleLowerCase(),
|
|
372
425
|
route,
|
|
373
|
-
|
|
426
|
+
rootPath,
|
|
374
427
|
routeHandler.apiData
|
|
375
428
|
));
|
|
376
429
|
}
|
|
377
430
|
}
|
|
378
431
|
const metadata = (0, import_package_metadata.default)();
|
|
432
|
+
const pathsAndComponents = {
|
|
433
|
+
paths: bundlePaths(validRoutes, schemas),
|
|
434
|
+
components: {
|
|
435
|
+
schemas: bundleSchemas(schemas),
|
|
436
|
+
securitySchemes
|
|
437
|
+
}
|
|
438
|
+
};
|
|
379
439
|
return {
|
|
380
440
|
openapi: "3.1.0",
|
|
381
441
|
info: {
|
|
382
442
|
title: metadata.serviceName,
|
|
383
443
|
version: metadata.version
|
|
384
444
|
},
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
},
|
|
445
|
+
servers,
|
|
446
|
+
...clearUnusedSchemas(pathsAndComponents),
|
|
447
|
+
security,
|
|
389
448
|
tags: []
|
|
390
449
|
};
|
|
391
450
|
}
|
package/dist/index.d.cts
CHANGED
|
@@ -1,11 +1,17 @@
|
|
|
1
1
|
import { OpenApiDocument } from '@omer-x/openapi-types';
|
|
2
|
+
import { ComponentsObject } from '@omer-x/openapi-types/components';
|
|
3
|
+
import { ServerObject } from '@omer-x/openapi-types/server';
|
|
2
4
|
import { ZodType } from 'zod';
|
|
3
5
|
|
|
4
6
|
type GeneratorOptions = {
|
|
5
7
|
include?: string[];
|
|
6
8
|
exclude?: string[];
|
|
7
9
|
routeDefinerName?: string;
|
|
10
|
+
rootPath?: string;
|
|
11
|
+
servers?: ServerObject[];
|
|
12
|
+
security?: OpenApiDocument["security"];
|
|
13
|
+
securitySchemes?: ComponentsObject["securitySchemes"];
|
|
8
14
|
};
|
|
9
|
-
declare function generateOpenApiSpec(schemas: Record<string, ZodType>, { include: includeOption, exclude: excludeOption, routeDefinerName, }?: GeneratorOptions): Promise<Omit<OpenApiDocument, "components"> & Required<Pick<OpenApiDocument, "components">>>;
|
|
15
|
+
declare function generateOpenApiSpec(schemas: Record<string, ZodType>, { include: includeOption, exclude: excludeOption, routeDefinerName, rootPath: additionalRootPath, servers, security, securitySchemes, }?: GeneratorOptions): Promise<Omit<OpenApiDocument, "components"> & Required<Pick<OpenApiDocument, "components">>>;
|
|
10
16
|
|
|
11
17
|
export { generateOpenApiSpec as default };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,11 +1,17 @@
|
|
|
1
1
|
import { OpenApiDocument } from '@omer-x/openapi-types';
|
|
2
|
+
import { ComponentsObject } from '@omer-x/openapi-types/components';
|
|
3
|
+
import { ServerObject } from '@omer-x/openapi-types/server';
|
|
2
4
|
import { ZodType } from 'zod';
|
|
3
5
|
|
|
4
6
|
type GeneratorOptions = {
|
|
5
7
|
include?: string[];
|
|
6
8
|
exclude?: string[];
|
|
7
9
|
routeDefinerName?: string;
|
|
10
|
+
rootPath?: string;
|
|
11
|
+
servers?: ServerObject[];
|
|
12
|
+
security?: OpenApiDocument["security"];
|
|
13
|
+
securitySchemes?: ComponentsObject["securitySchemes"];
|
|
8
14
|
};
|
|
9
|
-
declare function generateOpenApiSpec(schemas: Record<string, ZodType>, { include: includeOption, exclude: excludeOption, routeDefinerName, }?: GeneratorOptions): Promise<Omit<OpenApiDocument, "components"> & Required<Pick<OpenApiDocument, "components">>>;
|
|
15
|
+
declare function generateOpenApiSpec(schemas: Record<string, ZodType>, { include: includeOption, exclude: excludeOption, routeDefinerName, rootPath: additionalRootPath, servers, security, securitySchemes, }?: GeneratorOptions): Promise<Omit<OpenApiDocument, "components"> & Required<Pick<OpenApiDocument, "components">>>;
|
|
10
16
|
|
|
11
17
|
export { generateOpenApiSpec as default };
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,37 @@
|
|
|
1
1
|
// src/core/generateOpenApiSpec.ts
|
|
2
|
+
import path4 from "node:path";
|
|
2
3
|
import getPackageMetadata from "@omer-x/package-metadata";
|
|
3
4
|
|
|
5
|
+
// src/utils/object.ts
|
|
6
|
+
function omit(object, ...keys) {
|
|
7
|
+
return Object.fromEntries(Object.entries(object).filter(([key]) => !keys.includes(key)));
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
// src/core/clearUnusedSchemas.ts
|
|
11
|
+
function countReferences(schemaName, source) {
|
|
12
|
+
return (source.match(new RegExp(`"#/components/schemas/${schemaName}"`, "g")) ?? []).length;
|
|
13
|
+
}
|
|
14
|
+
function clearUnusedSchemas({
|
|
15
|
+
paths,
|
|
16
|
+
components
|
|
17
|
+
}) {
|
|
18
|
+
if (!components.schemas) return { paths, components };
|
|
19
|
+
const stringifiedPaths = JSON.stringify(paths);
|
|
20
|
+
const stringifiedSchemas = Object.fromEntries(Object.entries(components.schemas).map(([schemaName, schema]) => {
|
|
21
|
+
return [schemaName, JSON.stringify(schema)];
|
|
22
|
+
}));
|
|
23
|
+
return {
|
|
24
|
+
paths,
|
|
25
|
+
components: {
|
|
26
|
+
...components,
|
|
27
|
+
schemas: Object.fromEntries(Object.entries(components.schemas).filter(([schemaName]) => {
|
|
28
|
+
const otherSchemas = omit(stringifiedSchemas, schemaName);
|
|
29
|
+
return countReferences(schemaName, stringifiedPaths) > 0 || countReferences(schemaName, Object.values(otherSchemas).join("")) > 0;
|
|
30
|
+
}))
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
|
|
4
35
|
// src/core/dir.ts
|
|
5
36
|
import { constants } from "fs";
|
|
6
37
|
import fs from "fs/promises";
|
|
@@ -10,7 +41,7 @@ async function directoryExists(dirPath) {
|
|
|
10
41
|
try {
|
|
11
42
|
await fs.access(dirPath, constants.F_OK);
|
|
12
43
|
return true;
|
|
13
|
-
} catch
|
|
44
|
+
} catch {
|
|
14
45
|
return false;
|
|
15
46
|
}
|
|
16
47
|
}
|
|
@@ -40,8 +71,19 @@ function filterDirectoryItems(rootPath, items, include, exclude) {
|
|
|
40
71
|
});
|
|
41
72
|
}
|
|
42
73
|
|
|
43
|
-
// src/core/
|
|
74
|
+
// src/core/isDocumentedRoute.ts
|
|
44
75
|
import fs2 from "node:fs/promises";
|
|
76
|
+
async function isDocumentedRoute(routePath2) {
|
|
77
|
+
try {
|
|
78
|
+
const rawCode = await fs2.readFile(routePath2, "utf-8");
|
|
79
|
+
return rawCode.includes("@omer-x/next-openapi-route-handler");
|
|
80
|
+
} catch {
|
|
81
|
+
return false;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// src/core/next.ts
|
|
86
|
+
import fs3 from "node:fs/promises";
|
|
45
87
|
import path2 from "node:path";
|
|
46
88
|
|
|
47
89
|
// src/core/middleware.ts
|
|
@@ -102,7 +144,7 @@ function safeEval(code, routePath) {
|
|
|
102
144
|
}
|
|
103
145
|
}
|
|
104
146
|
async function getRouteExports(routePath2, routeDefinerName, schemas) {
|
|
105
|
-
const rawCode = await
|
|
147
|
+
const rawCode = await fs3.readFile(routePath2, "utf-8");
|
|
106
148
|
const middlewareName = detectMiddlewareName(rawCode);
|
|
107
149
|
const code2 = transpile(rawCode, routeDefinerName, middlewareName);
|
|
108
150
|
const fixedCode = Object.keys(schemas).reduce(injectSchemas, code2);
|
|
@@ -136,6 +178,13 @@ function verifyOptions(include, exclude) {
|
|
|
136
178
|
};
|
|
137
179
|
}
|
|
138
180
|
|
|
181
|
+
// src/core/getRoutePathName.ts
|
|
182
|
+
import path3 from "node:path";
|
|
183
|
+
function getRoutePathName(filePath, rootPath) {
|
|
184
|
+
const dirName = path3.dirname(filePath);
|
|
185
|
+
return "/" + path3.relative(rootPath, dirName).replaceAll("[", "{").replaceAll("]", "}").replaceAll("\\", "/");
|
|
186
|
+
}
|
|
187
|
+
|
|
139
188
|
// src/utils/deepEqual.ts
|
|
140
189
|
function deepEqual(a, b) {
|
|
141
190
|
if (typeof a !== typeof b) return false;
|
|
@@ -280,9 +329,6 @@ function maskOperationSchemas(operation, storedSchemas) {
|
|
|
280
329
|
}
|
|
281
330
|
|
|
282
331
|
// src/core/route.ts
|
|
283
|
-
function getRoutePathName(filePath, rootPath) {
|
|
284
|
-
return filePath.replace(rootPath, "").replace("[", "{").replace("]", "}").replaceAll("\\", "/").replace("/route.ts", "");
|
|
285
|
-
}
|
|
286
332
|
function createRouteRecord(method, filePath, rootPath, apiData) {
|
|
287
333
|
return {
|
|
288
334
|
method: method.toLocaleLowerCase(),
|
|
@@ -319,37 +365,50 @@ function bundleSchemas(schemas) {
|
|
|
319
365
|
async function generateOpenApiSpec(schemas, {
|
|
320
366
|
include: includeOption = [],
|
|
321
367
|
exclude: excludeOption = [],
|
|
322
|
-
routeDefinerName = "defineRoute"
|
|
368
|
+
routeDefinerName = "defineRoute",
|
|
369
|
+
rootPath: additionalRootPath,
|
|
370
|
+
servers,
|
|
371
|
+
security,
|
|
372
|
+
securitySchemes
|
|
323
373
|
} = {}) {
|
|
324
374
|
const verifiedOptions = verifyOptions(includeOption, excludeOption);
|
|
325
375
|
const appFolderPath = await findAppFolderPath();
|
|
326
376
|
if (!appFolderPath) throw new Error("This is not a Next.js application!");
|
|
327
|
-
const
|
|
328
|
-
const
|
|
377
|
+
const rootPath = additionalRootPath ? path4.resolve(appFolderPath, "./" + additionalRootPath) : appFolderPath;
|
|
378
|
+
const routes = await getDirectoryItems(rootPath, "route.ts");
|
|
379
|
+
const verifiedRoutes = filterDirectoryItems(rootPath, routes, verifiedOptions.include, verifiedOptions.exclude);
|
|
329
380
|
const validRoutes = [];
|
|
330
381
|
for (const route of verifiedRoutes) {
|
|
382
|
+
const isDocumented = await isDocumentedRoute(route);
|
|
383
|
+
if (!isDocumented) continue;
|
|
331
384
|
const exportedRouteHandlers = await getRouteExports(route, routeDefinerName, schemas);
|
|
332
385
|
for (const [method, routeHandler] of Object.entries(exportedRouteHandlers)) {
|
|
333
386
|
if (!routeHandler || !routeHandler.apiData) continue;
|
|
334
387
|
validRoutes.push(createRouteRecord(
|
|
335
388
|
method.toLocaleLowerCase(),
|
|
336
389
|
route,
|
|
337
|
-
|
|
390
|
+
rootPath,
|
|
338
391
|
routeHandler.apiData
|
|
339
392
|
));
|
|
340
393
|
}
|
|
341
394
|
}
|
|
342
395
|
const metadata = getPackageMetadata();
|
|
396
|
+
const pathsAndComponents = {
|
|
397
|
+
paths: bundlePaths(validRoutes, schemas),
|
|
398
|
+
components: {
|
|
399
|
+
schemas: bundleSchemas(schemas),
|
|
400
|
+
securitySchemes
|
|
401
|
+
}
|
|
402
|
+
};
|
|
343
403
|
return {
|
|
344
404
|
openapi: "3.1.0",
|
|
345
405
|
info: {
|
|
346
406
|
title: metadata.serviceName,
|
|
347
407
|
version: metadata.version
|
|
348
408
|
},
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
},
|
|
409
|
+
servers,
|
|
410
|
+
...clearUnusedSchemas(pathsAndComponents),
|
|
411
|
+
security,
|
|
353
412
|
tags: []
|
|
354
413
|
};
|
|
355
414
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@omer-x/next-openapi-json-generator",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"description": "a Next.js plugin to generate OpenAPI documentation from route handlers",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"next.js",
|
|
@@ -48,20 +48,20 @@
|
|
|
48
48
|
"@omer-x/package-metadata": "^1.0.0",
|
|
49
49
|
"minimatch": "^10.0.1",
|
|
50
50
|
"typescript": "^5.6.3",
|
|
51
|
-
"zod-to-json-schema": "^3.23.
|
|
51
|
+
"zod-to-json-schema": "^3.23.5"
|
|
52
52
|
},
|
|
53
53
|
"devDependencies": {
|
|
54
|
-
"@omer-x/eslint-config": "^
|
|
55
|
-
"@
|
|
56
|
-
"
|
|
57
|
-
"
|
|
58
|
-
"semantic-release": "^24.1.3",
|
|
54
|
+
"@omer-x/eslint-config": "^2.0.2",
|
|
55
|
+
"@types/node": "^22.8.4",
|
|
56
|
+
"eslint": "^9.13.0",
|
|
57
|
+
"semantic-release": "^24.2.0",
|
|
59
58
|
"ts-jest": "^29.2.5",
|
|
60
59
|
"ts-node": "^10.9.2",
|
|
61
|
-
"tsup": "^8.3.
|
|
60
|
+
"tsup": "^8.3.5",
|
|
62
61
|
"zod": "^3.23.8"
|
|
63
62
|
},
|
|
64
63
|
"peerDependencies": {
|
|
65
|
-
"@omer-x/next-openapi-route-handler": "^1"
|
|
64
|
+
"@omer-x/next-openapi-route-handler": "^1",
|
|
65
|
+
"@omer-x/openapi-types": "^1"
|
|
66
66
|
}
|
|
67
67
|
}
|