@omer-x/next-openapi-json-generator 1.1.2 → 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
  }
@@ -183,6 +214,13 @@ function verifyOptions(include, exclude) {
183
214
  };
184
215
  }
185
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
+
186
224
  // src/utils/deepEqual.ts
187
225
  function deepEqual(a, b) {
188
226
  if (typeof a !== typeof b) return false;
@@ -327,9 +365,6 @@ function maskOperationSchemas(operation, storedSchemas) {
327
365
  }
328
366
 
329
367
  // src/core/route.ts
330
- function getRoutePathName(filePath, rootPath) {
331
- return filePath.replace(rootPath, "").replace("[", "{").replace("]", "}").replaceAll("\\", "/").replace("/route.ts", "");
332
- }
333
368
  function createRouteRecord(method, filePath, rootPath, apiData) {
334
369
  return {
335
370
  method: method.toLocaleLowerCase(),
@@ -366,13 +401,18 @@ function bundleSchemas(schemas) {
366
401
  async function generateOpenApiSpec(schemas, {
367
402
  include: includeOption = [],
368
403
  exclude: excludeOption = [],
369
- routeDefinerName = "defineRoute"
404
+ routeDefinerName = "defineRoute",
405
+ rootPath: additionalRootPath,
406
+ servers,
407
+ security,
408
+ securitySchemes
370
409
  } = {}) {
371
410
  const verifiedOptions = verifyOptions(includeOption, excludeOption);
372
411
  const appFolderPath = await findAppFolderPath();
373
412
  if (!appFolderPath) throw new Error("This is not a Next.js application!");
374
- const routes = await getDirectoryItems(appFolderPath, "route.ts");
375
- 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);
376
416
  const validRoutes = [];
377
417
  for (const route of verifiedRoutes) {
378
418
  const isDocumented = await isDocumentedRoute(route);
@@ -383,22 +423,28 @@ async function generateOpenApiSpec(schemas, {
383
423
  validRoutes.push(createRouteRecord(
384
424
  method.toLocaleLowerCase(),
385
425
  route,
386
- appFolderPath,
426
+ rootPath,
387
427
  routeHandler.apiData
388
428
  ));
389
429
  }
390
430
  }
391
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
+ };
392
439
  return {
393
440
  openapi: "3.1.0",
394
441
  info: {
395
442
  title: metadata.serviceName,
396
443
  version: metadata.version
397
444
  },
398
- paths: bundlePaths(validRoutes, schemas),
399
- components: {
400
- schemas: bundleSchemas(schemas)
401
- },
445
+ servers,
446
+ ...clearUnusedSchemas(pathsAndComponents),
447
+ security,
402
448
  tags: []
403
449
  };
404
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
  }
@@ -147,6 +178,13 @@ function verifyOptions(include, exclude) {
147
178
  };
148
179
  }
149
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
+
150
188
  // src/utils/deepEqual.ts
151
189
  function deepEqual(a, b) {
152
190
  if (typeof a !== typeof b) return false;
@@ -291,9 +329,6 @@ function maskOperationSchemas(operation, storedSchemas) {
291
329
  }
292
330
 
293
331
  // src/core/route.ts
294
- function getRoutePathName(filePath, rootPath) {
295
- return filePath.replace(rootPath, "").replace("[", "{").replace("]", "}").replaceAll("\\", "/").replace("/route.ts", "");
296
- }
297
332
  function createRouteRecord(method, filePath, rootPath, apiData) {
298
333
  return {
299
334
  method: method.toLocaleLowerCase(),
@@ -330,13 +365,18 @@ function bundleSchemas(schemas) {
330
365
  async function generateOpenApiSpec(schemas, {
331
366
  include: includeOption = [],
332
367
  exclude: excludeOption = [],
333
- routeDefinerName = "defineRoute"
368
+ routeDefinerName = "defineRoute",
369
+ rootPath: additionalRootPath,
370
+ servers,
371
+ security,
372
+ securitySchemes
334
373
  } = {}) {
335
374
  const verifiedOptions = verifyOptions(includeOption, excludeOption);
336
375
  const appFolderPath = await findAppFolderPath();
337
376
  if (!appFolderPath) throw new Error("This is not a Next.js application!");
338
- const routes = await getDirectoryItems(appFolderPath, "route.ts");
339
- 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);
340
380
  const validRoutes = [];
341
381
  for (const route of verifiedRoutes) {
342
382
  const isDocumented = await isDocumentedRoute(route);
@@ -347,22 +387,28 @@ async function generateOpenApiSpec(schemas, {
347
387
  validRoutes.push(createRouteRecord(
348
388
  method.toLocaleLowerCase(),
349
389
  route,
350
- appFolderPath,
390
+ rootPath,
351
391
  routeHandler.apiData
352
392
  ));
353
393
  }
354
394
  }
355
395
  const metadata = getPackageMetadata();
396
+ const pathsAndComponents = {
397
+ paths: bundlePaths(validRoutes, schemas),
398
+ components: {
399
+ schemas: bundleSchemas(schemas),
400
+ securitySchemes
401
+ }
402
+ };
356
403
  return {
357
404
  openapi: "3.1.0",
358
405
  info: {
359
406
  title: metadata.serviceName,
360
407
  version: metadata.version
361
408
  },
362
- paths: bundlePaths(validRoutes, schemas),
363
- components: {
364
- schemas: bundleSchemas(schemas)
365
- },
409
+ servers,
410
+ ...clearUnusedSchemas(pathsAndComponents),
411
+ security,
366
412
  tags: []
367
413
  };
368
414
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@omer-x/next-openapi-json-generator",
3
- "version": "1.1.2",
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
  }