@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 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 (err) {
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/next.ts
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 import_promises2.default.readFile(routePath2, "utf-8");
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 routes = await getDirectoryItems(appFolderPath, "route.ts");
364
- const verifiedRoutes = filterDirectoryItems(appFolderPath, routes, verifiedOptions.include, verifiedOptions.exclude);
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
- appFolderPath,
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
- paths: bundlePaths(validRoutes, schemas),
386
- components: {
387
- schemas: bundleSchemas(schemas)
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 (err) {
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/next.ts
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 fs2.readFile(routePath2, "utf-8");
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 routes = await getDirectoryItems(appFolderPath, "route.ts");
328
- const verifiedRoutes = filterDirectoryItems(appFolderPath, routes, verifiedOptions.include, verifiedOptions.exclude);
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
- appFolderPath,
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
- paths: bundlePaths(validRoutes, schemas),
350
- components: {
351
- schemas: bundleSchemas(schemas)
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.1.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.3"
51
+ "zod-to-json-schema": "^3.23.5"
52
52
  },
53
53
  "devDependencies": {
54
- "@omer-x/eslint-config": "^1.0.7",
55
- "@omer-x/openapi-types": "^0.2.1",
56
- "@types/node": "^22.7.9",
57
- "eslint": "^8.57.1",
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.0",
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
  }