@spikers/next-openapi-json-generator 1.1.2 → 2.0.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/LICENSE CHANGED
@@ -1,21 +1,21 @@
1
- MIT License
2
-
3
- Copyright (c) 2024 Omer Mecitoglu
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in all
13
- copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- SOFTWARE.
1
+ MIT License
2
+
3
+ Copyright (c) 2024 Omer Mecitoglu
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,211 +1,211 @@
1
- # Next OpenAPI JSON Generator
2
-
3
- [![npm version](https://img.shields.io/npm/v/@omer-x/next-openapi-json-generator?logo=npm&logoColor=CB3837&color=CB3837)](https://www.npmjs.com/package/@omer-x/next-openapi-json-generator)
4
- [![npm downloads](https://img.shields.io/npm/dm/@omer-x/next-openapi-json-generator?logo=data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA1MTIgNTEyIj48IS0tIUZvbnQgQXdlc29tZSBGcmVlIDYuNi4wIGJ5IEBmb250YXdlc29tZSAtIGh0dHBzOi8vZm9udGF3ZXNvbWUuY29tIExpY2Vuc2UgLSBodHRwczovL2ZvbnRhd2Vzb21lLmNvbS9saWNlbnNlL2ZyZWUgQ29weXJpZ2h0IDIwMjQgRm9udGljb25zLCBJbmMuLS0+PHBhdGggZD0iTTI4OCAzMmMwLTE3LjctMTQuMy0zMi0zMi0zMnMtMzIgMTQuMy0zMiAzMmwwIDI0Mi43LTczLjQtNzMuNGMtMTIuNS0xMi41LTMyLjgtMTIuNS00NS4zIDBzLTEyLjUgMzIuOCAwIDQ1LjNsMTI4IDEyOGMxMi41IDEyLjUgMzIuOCAxMi41IDQ1LjMgMGwxMjgtMTI4YzEyLjUtMTIuNSAxMi41LTMyLjggMC00NS4zcy0zMi44LTEyLjUtNDUuMyAwTDI4OCAyNzQuNyAyODggMzJ6TTY0IDM1MmMtMzUuMyAwLTY0IDI4LjctNjQgNjRsMCAzMmMwIDM1LjMgMjguNyA2NCA2NCA2NGwzODQgMGMzNS4zIDAgNjQtMjguNyA2NC02NGwwLTMyYzAtMzUuMy0yOC43LTY0LTY0LTY0bC0xMDEuNSAwLTQ1LjMgNDUuM2MtMjUgMjUtNjUuNSAyNS05MC41IDBMMTY1LjUgMzUyIDY0IDM1MnptMzY4IDU2YTI0IDI0IDAgMSAxIDAgNDggMjQgMjQgMCAxIDEgMC00OHoiIGZpbGw9IiMwMDc4MjAiIC8+PC9zdmc+&color=007820)](https://www.npmjs.com/package/@omer-x/next-openapi-json-generator)
5
- [![codecov](https://codecov.io/gh/omermecitoglu/next-openapi-json-generator/branch/main/graph/badge.svg)](https://codecov.io/gh/omermecitoglu/next-openapi-json-generator)
6
- [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg?logo=data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA2NDAgNTEyIj48IS0tIUZvbnQgQXdlc29tZSBGcmVlIDYuNi4wIGJ5IEBmb250YXdlc29tZSAtIGh0dHBzOi8vZm9udGF3ZXNvbWUuY29tIExpY2Vuc2UgLSBodHRwczovL2ZvbnRhd2Vzb21lLmNvbS9saWNlbnNlL2ZyZWUgQ29weXJpZ2h0IDIwMjQgRm9udGljb25zLCBJbmMuLS0+PHBhdGggZD0iTTM4NCAzMmwxMjggMGMxNy43IDAgMzIgMTQuMyAzMiAzMnMtMTQuMyAzMi0zMiAzMkwzOTguNCA5NmMtNS4yIDI1LjgtMjIuOSA0Ny4xLTQ2LjQgNTcuM0wzNTIgNDQ4bDE2MCAwYzE3LjcgMCAzMiAxNC4zIDMyIDMycy0xNC4zIDMyLTMyIDMybC0xOTIgMC0xOTIgMGMtMTcuNyAwLTMyLTE0LjMtMzItMzJzMTQuMy0zMiAzMi0zMmwxNjAgMCAwLTI5NC43Yy0yMy41LTEwLjMtNDEuMi0zMS42LTQ2LjQtNTcuM0wxMjggOTZjLTE3LjcgMC0zMi0xNC4zLTMyLTMyczE0LjMtMzIgMzItMzJsMTI4IDBjMTQuNi0xOS40IDM3LjgtMzIgNjQtMzJzNDkuNCAxMi42IDY0IDMyem01NS42IDI4OGwxNDQuOSAwTDUxMiAxOTUuOCA0MzkuNiAzMjB6TTUxMiA0MTZjLTYyLjkgMC0xMTUuMi0zNC0xMjYtNzguOWMtMi42LTExIDEtMjIuMyA2LjctMzIuMWw5NS4yLTE2My4yYzUtOC42IDE0LjItMTMuOCAyNC4xLTEzLjhzMTkuMSA1LjMgMjQuMSAxMy44bDk1LjIgMTYzLjJjNS43IDkuOCA5LjMgMjEuMSA2LjcgMzIuMUM2MjcuMiAzODIgNTc0LjkgNDE2IDUxMiA0MTZ6TTEyNi44IDE5NS44TDU0LjQgMzIwbDE0NC45IDBMMTI2LjggMTk1Ljh6TS45IDMzNy4xYy0yLjYtMTEgMS0yMi4zIDYuNy0zMi4xbDk1LjItMTYzLjJjNS04LjYgMTQuMi0xMy44IDI0LjEtMTMuOHMxOS4xIDUuMyAyNC4xIDEzLjhsOTUuMiAxNjMuMmM1LjcgOS44IDkuMyAyMS4xIDYuNyAzMi4xQzI0MiAzODIgMTg5LjcgNDE2IDEyNi44IDQxNlMxMS43IDM4MiAuOSAzMzcuMXoiIGZpbGw9IiNEMEE4MUMiIC8+PC9zdmc+)](https://opensource.org/licenses/MIT)
7
- [![GitHub last commit](https://img.shields.io/github/last-commit/omermecitoglu/next-openapi-json-generator?color=c977be&logo=data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA2NDAgNTEyIj48IS0tIUZvbnQgQXdlc29tZSBGcmVlIDYuNi4wIGJ5IEBmb250YXdlc29tZSAtIGh0dHBzOi8vZm9udGF3ZXNvbWUuY29tIExpY2Vuc2UgLSBodHRwczovL2ZvbnRhd2Vzb21lLmNvbS9saWNlbnNlL2ZyZWUgQ29weXJpZ2h0IDIwMjQgRm9udGljb25zLCBJbmMuLS0+PHBhdGggZD0iTTMyMCAzMzZhODAgODAgMCAxIDAgMC0xNjAgODAgODAgMCAxIDAgMCAxNjB6bTE1Ni44LTQ4QzQ2MiAzNjEgMzk3LjQgNDE2IDMyMCA0MTZzLTE0Mi01NS0xNTYuOC0xMjhMMzIgMjg4Yy0xNy43IDAtMzItMTQuMy0zMi0zMnMxNC4zLTMyIDMyLTMybDEzMS4yIDBDMTc4IDE1MSAyNDIuNiA5NiAzMjAgOTZzMTQyIDU1IDE1Ni44IDEyOEw2MDggMjI0YzE3LjcgMCAzMiAxNC4zIDMyIDMycy0xNC4zIDMyLTMyIDMybC0xMzEuMiAweiIgZmlsbD0iI0M5NzdCRSIgLz48L3N2Zz4=)](https://github.com/omermecitoglu/next-openapi-json-generator/commits/main/)
8
- [![GitHub issues](https://img.shields.io/github/issues/omermecitoglu/next-openapi-json-generator?color=a38eed&logo=data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxNiAxNiIgd2lkdGg9IjE2IiBoZWlnaHQ9IjE2Ij4KICA8cGF0aCBkPSJNOCA5LjVhMS41IDEuNSAwIDEgMCAwLTMgMS41IDEuNSAwIDAgMCAwIDNaIiBmaWxsPSIjQTM4RUVEIj48L3BhdGg+CiAgPHBhdGggZD0iTTggMGE4IDggMCAxIDEgMCAxNkE4IDggMCAwIDEgOCAwWk0xLjUgOGE2LjUgNi41IDAgMSAwIDEzIDAgNi41IDYuNSAwIDAgMC0xMyAwWiIgZmlsbD0iI0EzOEVFRCI+PC9wYXRoPgo8L3N2Zz4=)](https://github.com/omermecitoglu/next-openapi-json-generator/issues)
9
- [![GitHub stars](https://img.shields.io/github/stars/omermecitoglu/next-openapi-json-generator?style=social)](https://github.com/omermecitoglu/next-openapi-json-generator)
10
-
11
- ## Overview
12
-
13
- `Next OpenAPI JSON Generator` is an open-source Next.js plugin that extracts and generates OpenAPI JSON specifications from your route handlers defined using `@omer-x/next-openapi-route-handler`. It simplifies the process of generating and maintaining OpenAPI documentation by leveraging TypeScript and Zod schemas.
14
-
15
- **Key Features:**
16
- - **Automated OpenAPI Generation**: Automatically generates OpenAPI JSON specs from your route handlers.
17
- - **TypeScript Integration**: Seamlessly integrates with TypeScript for strong type-checking.
18
- - **Zod Schema Support**: Uses Zod schemas for validation and generates JSON schemas for OpenAPI.
19
- - **Next.js Compatibility**: Works seamlessly with Next.js and integrates with other server-side libraries.
20
-
21
- > **Note:** This package works in conjunction with [`Next OpenAPI Route Handler`](https://www.npmjs.com/package/@omer-x/next-openapi-route-handler) to extract the generated OpenAPI JSON.
22
-
23
- ## Requirements
24
-
25
- To use `@omer-x/next-openapi-json-generator`, you'll need the following dependencies in your Next.js project:
26
-
27
- - [TypeScript](https://www.typescriptlang.org/) >= v5
28
- - [Next.js](https://nextjs.org/) >= v13
29
- - [Zod](https://zod.dev/) >= v3
30
- - [Next OpenAPI Route Handler](https://www.npmjs.com/package/@omer-x/next-openapi-route-handler)
31
-
32
- ## Installation
33
-
34
- To install this package, along with its peer dependency, run:
35
-
36
- ```sh
37
- npm install @omer-x/next-openapi-json-generator @omer-x/next-openapi-route-handler
38
- ```
39
-
40
- ## Usage
41
-
42
- The `generateOpenApiSpec` function is used to extract and generate the OpenAPI JSON specification from your defined models. Here's a description of how to use it:
43
-
44
- ### Example
45
-
46
- ```typescript
47
- import generateOpenApiSpec from "@omer-x/next-openapi-json-generator";
48
- import { NewUserDTO, UserDTO, UserPatchDTO } from "../models/user";
49
-
50
- const Page = async () => {
51
- const spec = await generateOpenApiSpec({
52
- UserDTO,
53
- NewUserDTO,
54
- UserPatchDTO,
55
- }, {
56
- // options
57
- });
58
- return <ReactSwagger spec={spec} />;
59
- };
60
-
61
- export default Page;
62
- ```
63
-
64
- ### Parameters
65
-
66
- The `generateOpenApiSpec` function takes an object with the following properties:
67
-
68
- | Property | Type | Description |
69
- | -------- | ------------------------------------------ | ---------------------------------------------------------------------------------- |
70
- | models | Record<string, [ZodType](https://zod.dev)> | An object where keys are model names and values are Zod schemas |
71
- | options | Object | `(Optional)` An object to customize the functionality of the generator (see below) |
72
-
73
- #### Options
74
-
75
- | Property | Type | Description |
76
- | ---------------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
77
- | include | string[] | `(Optional)` An array of strings which specifies the routes will be included to the JSON output |
78
- | exclude | string[] | `(Optional)` An array of strings which specifies the routes will be excluded from the JSON output |
79
- | routeDefinerName | string | `(Optional)` Name of the function that was exported from the [`Next OpenAPI Route Handler`](https://www.npmjs.com/package/@omer-x/next-openapi-route-handler) (Default: `defineRoute`) |
80
-
81
- ### Result
82
-
83
- The function returns a promise that resolves to an OpenAPI JSON specification.
84
-
85
- ```json
86
- {
87
- "openapi": "3.1.0",
88
- "info": {
89
- "title": "User Service",
90
- "version": "1.0.0"
91
- },
92
- "paths": {
93
- "/users": {
94
- "get": {
95
- ...
96
- },
97
- "post": {
98
- ...
99
- }
100
- },
101
- "/users/{id}": {
102
- "get": {
103
- "operationId": "getUser",
104
- "summary": "Get a specific user by ID",
105
- "description": "Retrieve details of a specific user by their ID",
106
- "tags": [
107
- "Users"
108
- ],
109
- "parameters": [
110
- {
111
- "in": "path",
112
- "name": "id",
113
- "required": true,
114
- "description": "ID of the user",
115
- "schema": {
116
- "type": "string",
117
- "description": "ID of the user"
118
- }
119
- }
120
- ],
121
- "responses": {
122
- "200": {
123
- "description": "User details retrieved successfully",
124
- "content": {
125
- "application/json": {
126
- "schema": {
127
- "$ref": "#/components/schemas/UserDTO"
128
- }
129
- }
130
- }
131
- },
132
- "404": {
133
- "description": "User not found"
134
- }
135
- }
136
- },
137
- "patch": {
138
- ...
139
- },
140
- "delete": {
141
- ...
142
- }
143
- }
144
- },
145
- "components": {
146
- "schemas": {
147
- "UserDTO": {
148
- "type": "object",
149
- "properties": {
150
- "id": {
151
- "type": "string",
152
- "format": "uuid",
153
- "description": "Unique identifier of the user"
154
- },
155
- "name": {
156
- "type": "string",
157
- "description": "Display name of the user"
158
- },
159
- "email": {
160
- "type": "string",
161
- "description": "Email address of the user"
162
- },
163
- "password": {
164
- "type": "string",
165
- "maxLength": 72,
166
- "description": "Encrypted password of the user"
167
- },
168
- "createdAt": {
169
- "type": "string",
170
- "format": "date-time",
171
- "description": "Creation date of the user"
172
- },
173
- "updatedAt": {
174
- "type": "string",
175
- "format": "date-time",
176
- "description": "Modification date of the user"
177
- }
178
- },
179
- "required": [
180
- "id",
181
- "name",
182
- "email",
183
- "password",
184
- "createdAt",
185
- "updatedAt"
186
- ],
187
- "additionalProperties": false,
188
- "description": "Represents the data of a user in the system."
189
- },
190
- "NewUserDTO": {
191
- ...
192
- },
193
- "UserPatchDTO": {
194
- ...
195
- }
196
- }
197
- }
198
- }
199
- ```
200
-
201
- [An example can be found here](https://github.com/omermecitoglu/example-user-service)
202
-
203
- ## Screenshots
204
-
205
- | <a href="https://i.imgur.com/ru3muBc.png" target="_blank"><img src="https://i.imgur.com/ru3muBc.png" alt="screenshot-1"></a> | <a href="https://i.imgur.com/utHaZ6X.png" target="_blank"><img src="https://i.imgur.com/utHaZ6X.png" alt="screenshot-2"></a> | <a href="https://i.imgur.com/2f24kPE.png" target="_blank"><img src="https://i.imgur.com/2f24kPE.png" alt="screenshot-3"></a> | <a href="https://i.imgur.com/z3KIJQ1.png" target="_blank"><img src="https://i.imgur.com/z3KIJQ1.png" alt="screenshot-4"></a> |
206
- |:--------------:|:--------------:|:--------------:|:--------------:|
207
- | <a href="https://i.imgur.com/IFKXOiX.png" target="_blank"><img src="https://i.imgur.com/IFKXOiX.png" alt="screenshot-5"></a> | <a href="https://i.imgur.com/xzVjAPq.png" target="_blank"><img src="https://i.imgur.com/xzVjAPq.png" alt="screenshot-6"></a> | <a href="https://i.imgur.com/HrWuHOR.png" target="_blank"><img src="https://i.imgur.com/HrWuHOR.png" alt="screenshot-7"></a> | |
208
-
209
- ## License
210
-
211
- This project is licensed under the MIT License. See the [LICENSE](LICENSE) file for details.
1
+ # Next OpenAPI JSON Generator
2
+
3
+ [![npm version](https://img.shields.io/npm/v/@omer-x/next-openapi-json-generator?logo=npm&logoColor=CB3837&color=CB3837)](https://www.npmjs.com/package/@omer-x/next-openapi-json-generator)
4
+ [![npm downloads](https://img.shields.io/npm/dm/@omer-x/next-openapi-json-generator?logo=data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA1MTIgNTEyIj48IS0tIUZvbnQgQXdlc29tZSBGcmVlIDYuNi4wIGJ5IEBmb250YXdlc29tZSAtIGh0dHBzOi8vZm9udGF3ZXNvbWUuY29tIExpY2Vuc2UgLSBodHRwczovL2ZvbnRhd2Vzb21lLmNvbS9saWNlbnNlL2ZyZWUgQ29weXJpZ2h0IDIwMjQgRm9udGljb25zLCBJbmMuLS0+PHBhdGggZD0iTTI4OCAzMmMwLTE3LjctMTQuMy0zMi0zMi0zMnMtMzIgMTQuMy0zMiAzMmwwIDI0Mi43LTczLjQtNzMuNGMtMTIuNS0xMi41LTMyLjgtMTIuNS00NS4zIDBzLTEyLjUgMzIuOCAwIDQ1LjNsMTI4IDEyOGMxMi41IDEyLjUgMzIuOCAxMi41IDQ1LjMgMGwxMjgtMTI4YzEyLjUtMTIuNSAxMi41LTMyLjggMC00NS4zcy0zMi44LTEyLjUtNDUuMyAwTDI4OCAyNzQuNyAyODggMzJ6TTY0IDM1MmMtMzUuMyAwLTY0IDI4LjctNjQgNjRsMCAzMmMwIDM1LjMgMjguNyA2NCA2NCA2NGwzODQgMGMzNS4zIDAgNjQtMjguNyA2NC02NGwwLTMyYzAtMzUuMy0yOC43LTY0LTY0LTY0bC0xMDEuNSAwLTQ1LjMgNDUuM2MtMjUgMjUtNjUuNSAyNS05MC41IDBMMTY1LjUgMzUyIDY0IDM1MnptMzY4IDU2YTI0IDI0IDAgMSAxIDAgNDggMjQgMjQgMCAxIDEgMC00OHoiIGZpbGw9IiMwMDc4MjAiIC8+PC9zdmc+&color=007820)](https://www.npmjs.com/package/@omer-x/next-openapi-json-generator)
5
+ [![codecov](https://codecov.io/gh/omermecitoglu/next-openapi-json-generator/branch/main/graph/badge.svg)](https://codecov.io/gh/omermecitoglu/next-openapi-json-generator)
6
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg?logo=data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA2NDAgNTEyIj48IS0tIUZvbnQgQXdlc29tZSBGcmVlIDYuNi4wIGJ5IEBmb250YXdlc29tZSAtIGh0dHBzOi8vZm9udGF3ZXNvbWUuY29tIExpY2Vuc2UgLSBodHRwczovL2ZvbnRhd2Vzb21lLmNvbS9saWNlbnNlL2ZyZWUgQ29weXJpZ2h0IDIwMjQgRm9udGljb25zLCBJbmMuLS0+PHBhdGggZD0iTTM4NCAzMmwxMjggMGMxNy43IDAgMzIgMTQuMyAzMiAzMnMtMTQuMyAzMi0zMiAzMkwzOTguNCA5NmMtNS4yIDI1LjgtMjIuOSA0Ny4xLTQ2LjQgNTcuM0wzNTIgNDQ4bDE2MCAwYzE3LjcgMCAzMiAxNC4zIDMyIDMycy0xNC4zIDMyLTMyIDMybC0xOTIgMC0xOTIgMGMtMTcuNyAwLTMyLTE0LjMtMzItMzJzMTQuMy0zMiAzMi0zMmwxNjAgMCAwLTI5NC43Yy0yMy41LTEwLjMtNDEuMi0zMS42LTQ2LjQtNTcuM0wxMjggOTZjLTE3LjcgMC0zMi0xNC4zLTMyLTMyczE0LjMtMzIgMzItMzJsMTI4IDBjMTQuNi0xOS40IDM3LjgtMzIgNjQtMzJzNDkuNCAxMi42IDY0IDMyem01NS42IDI4OGwxNDQuOSAwTDUxMiAxOTUuOCA0MzkuNiAzMjB6TTUxMiA0MTZjLTYyLjkgMC0xMTUuMi0zNC0xMjYtNzguOWMtMi42LTExIDEtMjIuMyA2LjctMzIuMWw5NS4yLTE2My4yYzUtOC42IDE0LjItMTMuOCAyNC4xLTEzLjhzMTkuMSA1LjMgMjQuMSAxMy44bDk1LjIgMTYzLjJjNS43IDkuOCA5LjMgMjEuMSA2LjcgMzIuMUM2MjcuMiAzODIgNTc0LjkgNDE2IDUxMiA0MTZ6TTEyNi44IDE5NS44TDU0LjQgMzIwbDE0NC45IDBMMTI2LjggMTk1Ljh6TS45IDMzNy4xYy0yLjYtMTEgMS0yMi4zIDYuNy0zMi4xbDk1LjItMTYzLjJjNS04LjYgMTQuMi0xMy44IDI0LjEtMTMuOHMxOS4xIDUuMyAyNC4xIDEzLjhsOTUuMiAxNjMuMmM1LjcgOS44IDkuMyAyMS4xIDYuNyAzMi4xQzI0MiAzODIgMTg5LjcgNDE2IDEyNi44IDQxNlMxMS43IDM4MiAuOSAzMzcuMXoiIGZpbGw9IiNEMEE4MUMiIC8+PC9zdmc+)](https://opensource.org/licenses/MIT)
7
+ [![GitHub last commit](https://img.shields.io/github/last-commit/omermecitoglu/next-openapi-json-generator?color=c977be&logo=data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA2NDAgNTEyIj48IS0tIUZvbnQgQXdlc29tZSBGcmVlIDYuNi4wIGJ5IEBmb250YXdlc29tZSAtIGh0dHBzOi8vZm9udGF3ZXNvbWUuY29tIExpY2Vuc2UgLSBodHRwczovL2ZvbnRhd2Vzb21lLmNvbS9saWNlbnNlL2ZyZWUgQ29weXJpZ2h0IDIwMjQgRm9udGljb25zLCBJbmMuLS0+PHBhdGggZD0iTTMyMCAzMzZhODAgODAgMCAxIDAgMC0xNjAgODAgODAgMCAxIDAgMCAxNjB6bTE1Ni44LTQ4QzQ2MiAzNjEgMzk3LjQgNDE2IDMyMCA0MTZzLTE0Mi01NS0xNTYuOC0xMjhMMzIgMjg4Yy0xNy43IDAtMzItMTQuMy0zMi0zMnMxNC4zLTMyIDMyLTMybDEzMS4yIDBDMTc4IDE1MSAyNDIuNiA5NiAzMjAgOTZzMTQyIDU1IDE1Ni44IDEyOEw2MDggMjI0YzE3LjcgMCAzMiAxNC4zIDMyIDMycy0xNC4zIDMyLTMyIDMybC0xMzEuMiAweiIgZmlsbD0iI0M5NzdCRSIgLz48L3N2Zz4=)](https://github.com/omermecitoglu/next-openapi-json-generator/commits/main/)
8
+ [![GitHub issues](https://img.shields.io/github/issues/omermecitoglu/next-openapi-json-generator?color=a38eed&logo=data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxNiAxNiIgd2lkdGg9IjE2IiBoZWlnaHQ9IjE2Ij4KICA8cGF0aCBkPSJNOCA5LjVhMS41IDEuNSAwIDEgMCAwLTMgMS41IDEuNSAwIDAgMCAwIDNaIiBmaWxsPSIjQTM4RUVEIj48L3BhdGg+CiAgPHBhdGggZD0iTTggMGE4IDggMCAxIDEgMCAxNkE4IDggMCAwIDEgOCAwWk0xLjUgOGE2LjUgNi41IDAgMSAwIDEzIDAgNi41IDYuNSAwIDAgMC0xMyAwWiIgZmlsbD0iI0EzOEVFRCI+PC9wYXRoPgo8L3N2Zz4=)](https://github.com/omermecitoglu/next-openapi-json-generator/issues)
9
+ [![GitHub stars](https://img.shields.io/github/stars/omermecitoglu/next-openapi-json-generator?style=social)](https://github.com/omermecitoglu/next-openapi-json-generator)
10
+
11
+ ## Overview
12
+
13
+ `Next OpenAPI JSON Generator` is an open-source Next.js plugin that extracts and generates OpenAPI JSON specifications from your route handlers defined using `@omer-x/next-openapi-route-handler`. It simplifies the process of generating and maintaining OpenAPI documentation by leveraging TypeScript and Zod schemas.
14
+
15
+ **Key Features:**
16
+ - **Automated OpenAPI Generation**: Automatically generates OpenAPI JSON specs from your route handlers.
17
+ - **TypeScript Integration**: Seamlessly integrates with TypeScript for strong type-checking.
18
+ - **Zod Schema Support**: Uses Zod schemas for validation and generates JSON schemas for OpenAPI.
19
+ - **Next.js Compatibility**: Works seamlessly with Next.js and integrates with other server-side libraries.
20
+
21
+ > **Note:** This package works in conjunction with [`Next OpenAPI Route Handler`](https://www.npmjs.com/package/@omer-x/next-openapi-route-handler) to extract the generated OpenAPI JSON.
22
+
23
+ ## Requirements
24
+
25
+ To use `@omer-x/next-openapi-json-generator`, you'll need the following dependencies in your Next.js project:
26
+
27
+ - [TypeScript](https://www.typescriptlang.org/) >= v5
28
+ - [Next.js](https://nextjs.org/) >= v13
29
+ - [Zod](https://zod.dev/) >= v3
30
+ - [Next OpenAPI Route Handler](https://www.npmjs.com/package/@omer-x/next-openapi-route-handler)
31
+
32
+ ## Installation
33
+
34
+ To install this package, along with its peer dependency, run:
35
+
36
+ ```sh
37
+ npm install @omer-x/next-openapi-json-generator @omer-x/next-openapi-route-handler
38
+ ```
39
+
40
+ ## Usage
41
+
42
+ The `generateOpenApiSpec` function is used to extract and generate the OpenAPI JSON specification from your defined models. Here's a description of how to use it:
43
+
44
+ ### Example
45
+
46
+ ```typescript
47
+ import generateOpenApiSpec from "@omer-x/next-openapi-json-generator";
48
+ import { NewUserDTO, UserDTO, UserPatchDTO } from "../models/user";
49
+
50
+ const Page = async () => {
51
+ const spec = await generateOpenApiSpec({
52
+ UserDTO,
53
+ NewUserDTO,
54
+ UserPatchDTO,
55
+ }, {
56
+ // options
57
+ });
58
+ return <ReactSwagger spec={spec} />;
59
+ };
60
+
61
+ export default Page;
62
+ ```
63
+
64
+ ### Parameters
65
+
66
+ The `generateOpenApiSpec` function takes an object with the following properties:
67
+
68
+ | Property | Type | Description |
69
+ | -------- | ------------------------------------------ | ---------------------------------------------------------------------------------- |
70
+ | models | Record<string, [ZodType](https://zod.dev)> | An object where keys are model names and values are Zod schemas |
71
+ | options | Object | `(Optional)` An object to customize the functionality of the generator (see below) |
72
+
73
+ #### Options
74
+
75
+ | Property | Type | Description |
76
+ | ---------------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
77
+ | include | string[] | `(Optional)` An array of strings which specifies the routes will be included to the JSON output |
78
+ | exclude | string[] | `(Optional)` An array of strings which specifies the routes will be excluded from the JSON output |
79
+ | routeDefinerName | string | `(Optional)` Name of the function that was exported from the [`Next OpenAPI Route Handler`](https://www.npmjs.com/package/@omer-x/next-openapi-route-handler) (Default: `defineRoute`) |
80
+
81
+ ### Result
82
+
83
+ The function returns a promise that resolves to an OpenAPI JSON specification.
84
+
85
+ ```json
86
+ {
87
+ "openapi": "3.1.0",
88
+ "info": {
89
+ "title": "User Service",
90
+ "version": "1.0.0"
91
+ },
92
+ "paths": {
93
+ "/users": {
94
+ "get": {
95
+ ...
96
+ },
97
+ "post": {
98
+ ...
99
+ }
100
+ },
101
+ "/users/{id}": {
102
+ "get": {
103
+ "operationId": "getUser",
104
+ "summary": "Get a specific user by ID",
105
+ "description": "Retrieve details of a specific user by their ID",
106
+ "tags": [
107
+ "Users"
108
+ ],
109
+ "parameters": [
110
+ {
111
+ "in": "path",
112
+ "name": "id",
113
+ "required": true,
114
+ "description": "ID of the user",
115
+ "schema": {
116
+ "type": "string",
117
+ "description": "ID of the user"
118
+ }
119
+ }
120
+ ],
121
+ "responses": {
122
+ "200": {
123
+ "description": "User details retrieved successfully",
124
+ "content": {
125
+ "application/json": {
126
+ "schema": {
127
+ "$ref": "#/components/schemas/UserDTO"
128
+ }
129
+ }
130
+ }
131
+ },
132
+ "404": {
133
+ "description": "User not found"
134
+ }
135
+ }
136
+ },
137
+ "patch": {
138
+ ...
139
+ },
140
+ "delete": {
141
+ ...
142
+ }
143
+ }
144
+ },
145
+ "components": {
146
+ "schemas": {
147
+ "UserDTO": {
148
+ "type": "object",
149
+ "properties": {
150
+ "id": {
151
+ "type": "string",
152
+ "format": "uuid",
153
+ "description": "Unique identifier of the user"
154
+ },
155
+ "name": {
156
+ "type": "string",
157
+ "description": "Display name of the user"
158
+ },
159
+ "email": {
160
+ "type": "string",
161
+ "description": "Email address of the user"
162
+ },
163
+ "password": {
164
+ "type": "string",
165
+ "maxLength": 72,
166
+ "description": "Encrypted password of the user"
167
+ },
168
+ "createdAt": {
169
+ "type": "string",
170
+ "format": "date-time",
171
+ "description": "Creation date of the user"
172
+ },
173
+ "updatedAt": {
174
+ "type": "string",
175
+ "format": "date-time",
176
+ "description": "Modification date of the user"
177
+ }
178
+ },
179
+ "required": [
180
+ "id",
181
+ "name",
182
+ "email",
183
+ "password",
184
+ "createdAt",
185
+ "updatedAt"
186
+ ],
187
+ "additionalProperties": false,
188
+ "description": "Represents the data of a user in the system."
189
+ },
190
+ "NewUserDTO": {
191
+ ...
192
+ },
193
+ "UserPatchDTO": {
194
+ ...
195
+ }
196
+ }
197
+ }
198
+ }
199
+ ```
200
+
201
+ [An example can be found here](https://github.com/omermecitoglu/example-user-service)
202
+
203
+ ## Screenshots
204
+
205
+ | <a href="https://i.imgur.com/ru3muBc.png" target="_blank"><img src="https://i.imgur.com/ru3muBc.png" alt="screenshot-1"></a> | <a href="https://i.imgur.com/utHaZ6X.png" target="_blank"><img src="https://i.imgur.com/utHaZ6X.png" alt="screenshot-2"></a> | <a href="https://i.imgur.com/2f24kPE.png" target="_blank"><img src="https://i.imgur.com/2f24kPE.png" alt="screenshot-3"></a> | <a href="https://i.imgur.com/z3KIJQ1.png" target="_blank"><img src="https://i.imgur.com/z3KIJQ1.png" alt="screenshot-4"></a> |
206
+ |:--------------:|:--------------:|:--------------:|:--------------:|
207
+ | <a href="https://i.imgur.com/IFKXOiX.png" target="_blank"><img src="https://i.imgur.com/IFKXOiX.png" alt="screenshot-5"></a> | <a href="https://i.imgur.com/xzVjAPq.png" target="_blank"><img src="https://i.imgur.com/xzVjAPq.png" alt="screenshot-6"></a> | <a href="https://i.imgur.com/HrWuHOR.png" target="_blank"><img src="https://i.imgur.com/HrWuHOR.png" alt="screenshot-7"></a> | |
208
+
209
+ ## License
210
+
211
+ This project is licensed under the MIT License. See the [LICENSE](LICENSE) file for details.
package/dist/index.cjs CHANGED
@@ -91,7 +91,7 @@ async function getDirectoryItems(dirPath, targetFileName) {
91
91
  const children = await getDirectoryItems(itemPath, targetFileName);
92
92
  collection.push(...children);
93
93
  } else if (itemName === targetFileName) {
94
- collection.push(itemPath.split(import_node_path.default.sep).join("/").replace(/^[A-Z]:/i, ""));
94
+ collection.push(itemPath);
95
95
  }
96
96
  }
97
97
  return collection;
@@ -109,9 +109,9 @@ function filterDirectoryItems(rootPath, items, include, exclude) {
109
109
 
110
110
  // src/core/isDocumentedRoute.ts
111
111
  var import_promises2 = __toESM(require("fs/promises"), 1);
112
- async function isDocumentedRoute(routePath) {
112
+ async function isDocumentedRoute(routePath2) {
113
113
  try {
114
- const rawCode = await import_promises2.default.readFile(routePath, "utf-8");
114
+ const rawCode = await import_promises2.default.readFile(routePath2, "utf-8");
115
115
  return rawCode.includes("@omer-x/next-openapi-route-handler");
116
116
  } catch {
117
117
  return false;
@@ -121,6 +121,8 @@ async function isDocumentedRoute(routePath) {
121
121
  // src/core/next.ts
122
122
  var import_promises3 = __toESM(require("fs/promises"), 1);
123
123
  var import_node_path2 = __toESM(require("path"), 1);
124
+ var import_next_openapi_route_handler = require("@omer-x/next-openapi-route-handler");
125
+ var import_zod = require("zod");
124
126
 
125
127
  // src/utils/generateRandomString.ts
126
128
  function generateRandomString(length) {
@@ -128,9 +130,9 @@ function generateRandomString(length) {
128
130
  }
129
131
 
130
132
  // src/utils/string-preservation.ts
131
- function preserveStrings(code) {
133
+ function preserveStrings(code2) {
132
134
  let replacements = {};
133
- const output = code.replace(/(['"`])((?:\\.|(?!\1).)*)\1/g, (match, quote, content) => {
135
+ const output = code2.replace(/(['"`])((?:\\.|(?!\1).)*)\1/g, (match, quote, content) => {
134
136
  const replacementId = generateRandomString(32);
135
137
  replacements = {
136
138
  ...replacements,
@@ -140,55 +142,61 @@ function preserveStrings(code) {
140
142
  });
141
143
  return { output, replacements };
142
144
  }
143
- function restoreStrings(code, replacements) {
144
- return code.replace(/<@~(.*?)~@>/g, (_, replacementId) => {
145
+ function restoreStrings(code2, replacements) {
146
+ return code2.replace(/<@~(.*?)~@>/g, (_, replacementId) => {
145
147
  return replacements[replacementId];
146
148
  });
147
149
  }
148
150
 
149
151
  // src/core/injectSchemas.ts
150
- function injectSchemas(code, refName) {
151
- const { output: preservedCode, replacements } = preserveStrings(code);
152
+ function injectSchemas(code2, refName) {
153
+ const { output: preservedCode, replacements } = preserveStrings(code2);
152
154
  const preservedCodeWithSchemasInjected = preservedCode.replace(new RegExp(`\\b${refName}\\.`, "g"), `global.schemas[${refName}].`).replace(new RegExp(`\\b${refName}\\b`, "g"), `"${refName}"`).replace(new RegExp(`queryParams:\\s*['"\`]${refName}['"\`]`, "g"), `queryParams: global.schemas["${refName}"]`).replace(new RegExp(`pathParams:\\s*['"\`]${refName}['"\`]`, "g"), `pathParams: global.schemas["${refName}"]`);
153
155
  return restoreStrings(preservedCodeWithSchemasInjected, replacements);
154
156
  }
155
157
 
156
158
  // src/core/middleware.ts
157
- function detectMiddlewareName(code) {
158
- const match = code.match(/middleware:\s*(\w+)/);
159
+ function detectMiddlewareName(code2) {
160
+ const match = code2.match(/middleware:\s*(\w+)/);
159
161
  return match ? match[1] : null;
160
162
  }
161
163
 
162
- // src/core/transpile.ts
163
- var import_typescript = require("typescript");
164
-
165
164
  // src/utils/removeImports.ts
166
- function removeImports(code) {
167
- return code.replace(/(^import\s+[^;]+;?$|^import\s+[^;]*\sfrom\s.+;?$)/gm, "").replace(/(^import\s+{[\s\S]+?}\s+from\s+["'][^"']+["'];?)/gm, "").trim();
165
+ function removeImports(code2) {
166
+ return code2.replace(/(^import\s+[^;]+;?$|^import\s+[^;]*\sfrom\s.+;?$)/gm, "").replace(/(^import\s+{[\s\S]+?}\s+from\s+["'][^"']+["'];?)/gm, "").trim();
168
167
  }
169
168
 
170
169
  // src/core/transpile.ts
171
- function fixExports(code) {
170
+ function fixExportsInCommonJS(code2) {
172
171
  const validMethods = ["GET", "POST", "PUT", "PATCH", "DELETE"];
173
172
  const exportFixer1 = validMethods.map((method) => `exports.${method} = void 0;
174
- `);
175
- const exportFixer2 = `module.exports = { ${validMethods.map((m) => `${m}: exports.${m}`).join(", ")} }`;
173
+ `).join("\n");
174
+ const exportFixer2 = `module.exports = { ${validMethods.map((m) => `${m}: exports.${m}`).join(", ")} };`;
176
175
  return `${exportFixer1}
177
- ${code}
176
+ ${code2}
178
177
  ${exportFixer2}`;
179
178
  }
180
179
  function injectMiddlewareFixer(middlewareName) {
181
180
  return `const ${middlewareName} = (handler) => handler;`;
182
181
  }
183
- function transpile(rawCode, routeDefinerName, middlewareName) {
184
- const code = fixExports(removeImports(rawCode));
182
+ function transpile(isCommonJS, rawCode, middlewareName, transpileModule) {
185
183
  const parts = [
186
- `import ${routeDefinerName} from '@omer-x/next-openapi-route-handler';`,
187
- "import z from 'zod';",
188
184
  middlewareName ? injectMiddlewareFixer(middlewareName) : "",
189
- code
185
+ removeImports(rawCode)
190
186
  ];
191
- return (0, import_typescript.transpile)(parts.join("\n"));
187
+ const output = transpileModule(parts.join("\n"), {
188
+ compilerOptions: {
189
+ module: isCommonJS ? 3 : 99,
190
+ target: 99,
191
+ sourceMap: false,
192
+ inlineSourceMap: false,
193
+ inlineSources: false
194
+ }
195
+ });
196
+ if (isCommonJS) {
197
+ return fixExportsInCommonJS(output.outputText);
198
+ }
199
+ return output.outputText;
192
200
  }
193
201
 
194
202
  // src/core/next.ts
@@ -203,33 +211,46 @@ async function findAppFolderPath() {
203
211
  }
204
212
  return null;
205
213
  }
206
- function safeEval(code, routePath) {
214
+ async function safeEval(code, routePath) {
207
215
  try {
208
- const fn = new Function("global", "require", `
209
- const module = { exports: {} };
210
- const exports = module.exports;
211
-
212
- const NextResponse = { json: () => ({}) };
213
- const next = { server: { NextResponse } };
214
-
215
- ${code}
216
-
217
- return module.exports;
218
- `);
219
- return fn(global, require);
216
+ if (typeof module !== "undefined" && typeof module.exports !== "undefined") {
217
+ return eval(code);
218
+ }
219
+ return await import(
220
+ /* webpackIgnore: true */
221
+ `data:text/javascript,${encodeURIComponent(code)}`
222
+ );
220
223
  } catch (error) {
221
224
  console.log(`An error occured while evaluating the route exports from "${routePath}"`);
222
225
  throw error;
223
226
  }
224
227
  }
225
- async function getRouteExports(routePath, routeDefinerName, schemas) {
226
- const rawCode = await import_promises3.default.readFile(routePath, "utf-8");
228
+ async function getModuleTranspiler() {
229
+ if (typeof require !== "undefined" && typeof exports !== "undefined") {
230
+ return require(
231
+ /* webpackIgnore: true */
232
+ "typescript"
233
+ ).transpileModule;
234
+ }
235
+ const { transpileModule } = await import(
236
+ /* webpackIgnore: true */
237
+ "typescript"
238
+ );
239
+ return transpileModule;
240
+ }
241
+ async function getRouteExports(routePath2, routeDefinerName, schemas) {
242
+ const rawCode = await import_promises3.default.readFile(routePath2, "utf-8");
227
243
  const middlewareName = detectMiddlewareName(rawCode);
228
- const code = transpile(rawCode, routeDefinerName, middlewareName);
229
- const fixedCode = Object.keys(schemas).reduce(injectSchemas, code);
244
+ const isCommonJS = typeof module !== "undefined" && typeof module.exports !== "undefined";
245
+ const code2 = transpile(isCommonJS, rawCode, middlewareName, await getModuleTranspiler());
246
+ const fixedCode = Object.keys(schemas).reduce(injectSchemas, code2);
247
+ global[routeDefinerName] = import_next_openapi_route_handler.defineRoute;
248
+ global.z = import_zod.z;
230
249
  global.schemas = schemas;
231
- const result = safeEval(fixedCode, routePath);
250
+ const result = await safeEval(fixedCode, routePath2);
232
251
  delete global.schemas;
252
+ delete global[routeDefinerName];
253
+ delete global.z;
233
254
  return result;
234
255
  }
235
256
 
@@ -257,7 +278,7 @@ function verifyOptions(include, exclude) {
257
278
  var import_node_path3 = __toESM(require("path"), 1);
258
279
  function getRoutePathName(filePath, rootPath) {
259
280
  const dirName = import_node_path3.default.dirname(filePath);
260
- return "/" + import_node_path3.default.relative(rootPath, dirName).replaceAll("[", "{").replaceAll("]", "}").replaceAll("\\", "/").replace(/\([^)]+\)\/|\/\([^)]+\)|\([^)]+\)/g, "");
281
+ return "/" + import_node_path3.default.relative(rootPath, dirName).replaceAll("[", "{").replaceAll("]", "}").replaceAll("\\", "/");
261
282
  }
262
283
 
263
284
  // src/utils/deepEqual.ts
@@ -283,36 +304,38 @@ function deepEqual(a, b) {
283
304
  }
284
305
 
285
306
  // src/core/zod-to-openapi.ts
286
- var import_zod_to_json_schema = require("zod-to-json-schema");
287
-
288
- // src/utils/zod-schema.ts
289
- function isFile(schema) {
290
- const file = new File([], "nothing.txt");
291
- const plainObject = { name: "test", size: 0 };
292
- const fileResult = schema.safeParse(file);
293
- const objectResult = schema.safeParse(plainObject);
294
- return fileResult.success && !objectResult.success;
295
- }
296
-
297
- // src/core/zod-to-openapi.ts
298
- function convertToOpenAPI(schema, isArray) {
299
- const result = (0, import_zod_to_json_schema.zodToJsonSchema)(isArray ? schema.array() : schema, {
300
- target: "openApi3",
301
- $refStrategy: "none"
302
- });
303
- if (result.type === "object" && result.properties) {
304
- for (const [propName, prop] of Object.entries(schema.shape)) {
305
- if (isFile(prop)) {
306
- result.properties[propName] = {
307
- type: "string",
308
- format: "binary",
309
- description: prop.description
310
- // contentEncoding: "base64", // swagger-ui-react doesn't support this
311
- };
312
- }
307
+ var import_zod2 = require("zod");
308
+ function fixSchema(schema) {
309
+ if ("unwrap" in schema && typeof schema.unwrap === "function") {
310
+ switch (schema._zod.def.type) {
311
+ case "nullable":
312
+ return fixSchema(schema.unwrap()).nullable();
313
+ case "optional":
314
+ return fixSchema(schema.unwrap()).optional();
315
+ case "readonly":
316
+ return fixSchema(schema.unwrap()).readonly();
317
+ case "array":
318
+ return fixSchema(schema.unwrap()).array();
319
+ case "nonoptional":
320
+ return fixSchema(schema.unwrap());
321
+ default:
322
+ throw new Error(`${schema._zod.def.type} type is not covered in fixSchema (@omer-x/next-openapi-json-generator")`);
313
323
  }
314
324
  }
315
- return result;
325
+ if (schema._zod.def.type === "date") {
326
+ return import_zod2.z.iso.datetime();
327
+ }
328
+ if (schema._zod.def.type === "object") {
329
+ const { shape } = schema;
330
+ const entries = Object.entries(shape);
331
+ const alteredEntries = entries.map(([propName, prop]) => [propName, fixSchema(prop)]);
332
+ const newShape = Object.fromEntries(alteredEntries);
333
+ return import_zod2.z.object(newShape);
334
+ }
335
+ return schema;
336
+ }
337
+ function convertToOpenAPI(schema, isArray) {
338
+ return import_zod2.z.toJSONSchema(fixSchema(isArray ? schema.array() : schema));
316
339
  }
317
340
 
318
341
  // src/core/mask.ts
@@ -480,7 +503,7 @@ async function generateOpenApiSpec(schemas, {
480
503
  securitySchemes
481
504
  }
482
505
  };
483
- return {
506
+ return JSON.parse(JSON.stringify({
484
507
  openapi: "3.1.0",
485
508
  info: {
486
509
  title: metadata.serviceName,
@@ -491,7 +514,7 @@ async function generateOpenApiSpec(schemas, {
491
514
  ...clearUnusedSchemasOption ? clearUnusedSchemas(pathsAndComponents) : pathsAndComponents,
492
515
  security,
493
516
  tags: []
494
- };
517
+ }));
495
518
  }
496
519
 
497
520
  // src/index.ts
package/dist/index.js CHANGED
@@ -62,7 +62,7 @@ async function getDirectoryItems(dirPath, targetFileName) {
62
62
  const children = await getDirectoryItems(itemPath, targetFileName);
63
63
  collection.push(...children);
64
64
  } else if (itemName === targetFileName) {
65
- collection.push(itemPath.split(path.sep).join("/").replace(/^[A-Z]:/i, ""));
65
+ collection.push(itemPath);
66
66
  }
67
67
  }
68
68
  return collection;
@@ -80,9 +80,9 @@ function filterDirectoryItems(rootPath, items, include, exclude) {
80
80
 
81
81
  // src/core/isDocumentedRoute.ts
82
82
  import fs2 from "fs/promises";
83
- async function isDocumentedRoute(routePath) {
83
+ async function isDocumentedRoute(routePath2) {
84
84
  try {
85
- const rawCode = await fs2.readFile(routePath, "utf-8");
85
+ const rawCode = await fs2.readFile(routePath2, "utf-8");
86
86
  return rawCode.includes("@omer-x/next-openapi-route-handler");
87
87
  } catch {
88
88
  return false;
@@ -92,6 +92,8 @@ async function isDocumentedRoute(routePath) {
92
92
  // src/core/next.ts
93
93
  import fs3 from "fs/promises";
94
94
  import path2 from "path";
95
+ import { defineRoute } from "@omer-x/next-openapi-route-handler";
96
+ import { z } from "zod";
95
97
 
96
98
  // src/utils/generateRandomString.ts
97
99
  function generateRandomString(length) {
@@ -99,9 +101,9 @@ function generateRandomString(length) {
99
101
  }
100
102
 
101
103
  // src/utils/string-preservation.ts
102
- function preserveStrings(code) {
104
+ function preserveStrings(code2) {
103
105
  let replacements = {};
104
- const output = code.replace(/(['"`])((?:\\.|(?!\1).)*)\1/g, (match, quote, content) => {
106
+ const output = code2.replace(/(['"`])((?:\\.|(?!\1).)*)\1/g, (match, quote, content) => {
105
107
  const replacementId = generateRandomString(32);
106
108
  replacements = {
107
109
  ...replacements,
@@ -111,55 +113,61 @@ function preserveStrings(code) {
111
113
  });
112
114
  return { output, replacements };
113
115
  }
114
- function restoreStrings(code, replacements) {
115
- return code.replace(/<@~(.*?)~@>/g, (_, replacementId) => {
116
+ function restoreStrings(code2, replacements) {
117
+ return code2.replace(/<@~(.*?)~@>/g, (_, replacementId) => {
116
118
  return replacements[replacementId];
117
119
  });
118
120
  }
119
121
 
120
122
  // src/core/injectSchemas.ts
121
- function injectSchemas(code, refName) {
122
- const { output: preservedCode, replacements } = preserveStrings(code);
123
+ function injectSchemas(code2, refName) {
124
+ const { output: preservedCode, replacements } = preserveStrings(code2);
123
125
  const preservedCodeWithSchemasInjected = preservedCode.replace(new RegExp(`\\b${refName}\\.`, "g"), `global.schemas[${refName}].`).replace(new RegExp(`\\b${refName}\\b`, "g"), `"${refName}"`).replace(new RegExp(`queryParams:\\s*['"\`]${refName}['"\`]`, "g"), `queryParams: global.schemas["${refName}"]`).replace(new RegExp(`pathParams:\\s*['"\`]${refName}['"\`]`, "g"), `pathParams: global.schemas["${refName}"]`);
124
126
  return restoreStrings(preservedCodeWithSchemasInjected, replacements);
125
127
  }
126
128
 
127
129
  // src/core/middleware.ts
128
- function detectMiddlewareName(code) {
129
- const match = code.match(/middleware:\s*(\w+)/);
130
+ function detectMiddlewareName(code2) {
131
+ const match = code2.match(/middleware:\s*(\w+)/);
130
132
  return match ? match[1] : null;
131
133
  }
132
134
 
133
- // src/core/transpile.ts
134
- import { transpile as tsTranspile } from "typescript";
135
-
136
135
  // src/utils/removeImports.ts
137
- function removeImports(code) {
138
- return code.replace(/(^import\s+[^;]+;?$|^import\s+[^;]*\sfrom\s.+;?$)/gm, "").replace(/(^import\s+{[\s\S]+?}\s+from\s+["'][^"']+["'];?)/gm, "").trim();
136
+ function removeImports(code2) {
137
+ return code2.replace(/(^import\s+[^;]+;?$|^import\s+[^;]*\sfrom\s.+;?$)/gm, "").replace(/(^import\s+{[\s\S]+?}\s+from\s+["'][^"']+["'];?)/gm, "").trim();
139
138
  }
140
139
 
141
140
  // src/core/transpile.ts
142
- function fixExports(code) {
141
+ function fixExportsInCommonJS(code2) {
143
142
  const validMethods = ["GET", "POST", "PUT", "PATCH", "DELETE"];
144
143
  const exportFixer1 = validMethods.map((method) => `exports.${method} = void 0;
145
- `);
146
- const exportFixer2 = `module.exports = { ${validMethods.map((m) => `${m}: exports.${m}`).join(", ")} }`;
144
+ `).join("\n");
145
+ const exportFixer2 = `module.exports = { ${validMethods.map((m) => `${m}: exports.${m}`).join(", ")} };`;
147
146
  return `${exportFixer1}
148
- ${code}
147
+ ${code2}
149
148
  ${exportFixer2}`;
150
149
  }
151
150
  function injectMiddlewareFixer(middlewareName) {
152
151
  return `const ${middlewareName} = (handler) => handler;`;
153
152
  }
154
- function transpile(rawCode, routeDefinerName, middlewareName) {
155
- const code = fixExports(removeImports(rawCode));
153
+ function transpile(isCommonJS, rawCode, middlewareName, transpileModule) {
156
154
  const parts = [
157
- `import ${routeDefinerName} from '@omer-x/next-openapi-route-handler';`,
158
- "import z from 'zod';",
159
155
  middlewareName ? injectMiddlewareFixer(middlewareName) : "",
160
- code
156
+ removeImports(rawCode)
161
157
  ];
162
- return tsTranspile(parts.join("\n"));
158
+ const output = transpileModule(parts.join("\n"), {
159
+ compilerOptions: {
160
+ module: isCommonJS ? 3 : 99,
161
+ target: 99,
162
+ sourceMap: false,
163
+ inlineSourceMap: false,
164
+ inlineSources: false
165
+ }
166
+ });
167
+ if (isCommonJS) {
168
+ return fixExportsInCommonJS(output.outputText);
169
+ }
170
+ return output.outputText;
163
171
  }
164
172
 
165
173
  // src/core/next.ts
@@ -174,33 +182,46 @@ async function findAppFolderPath() {
174
182
  }
175
183
  return null;
176
184
  }
177
- function safeEval(code, routePath) {
185
+ async function safeEval(code, routePath) {
178
186
  try {
179
- const fn = new Function("global", "require", `
180
- const module = { exports: {} };
181
- const exports = module.exports;
182
-
183
- const NextResponse = { json: () => ({}) };
184
- const next = { server: { NextResponse } };
185
-
186
- ${code}
187
-
188
- return module.exports;
189
- `);
190
- return fn(global, __require);
187
+ if (typeof module !== "undefined" && typeof module.exports !== "undefined") {
188
+ return eval(code);
189
+ }
190
+ return await import(
191
+ /* webpackIgnore: true */
192
+ `data:text/javascript,${encodeURIComponent(code)}`
193
+ );
191
194
  } catch (error) {
192
195
  console.log(`An error occured while evaluating the route exports from "${routePath}"`);
193
196
  throw error;
194
197
  }
195
198
  }
196
- async function getRouteExports(routePath, routeDefinerName, schemas) {
197
- const rawCode = await fs3.readFile(routePath, "utf-8");
199
+ async function getModuleTranspiler() {
200
+ if (typeof __require !== "undefined" && typeof exports !== "undefined") {
201
+ return __require(
202
+ /* webpackIgnore: true */
203
+ "typescript"
204
+ ).transpileModule;
205
+ }
206
+ const { transpileModule } = await import(
207
+ /* webpackIgnore: true */
208
+ "typescript"
209
+ );
210
+ return transpileModule;
211
+ }
212
+ async function getRouteExports(routePath2, routeDefinerName, schemas) {
213
+ const rawCode = await fs3.readFile(routePath2, "utf-8");
198
214
  const middlewareName = detectMiddlewareName(rawCode);
199
- const code = transpile(rawCode, routeDefinerName, middlewareName);
200
- const fixedCode = Object.keys(schemas).reduce(injectSchemas, code);
215
+ const isCommonJS = typeof module !== "undefined" && typeof module.exports !== "undefined";
216
+ const code2 = transpile(isCommonJS, rawCode, middlewareName, await getModuleTranspiler());
217
+ const fixedCode = Object.keys(schemas).reduce(injectSchemas, code2);
218
+ global[routeDefinerName] = defineRoute;
219
+ global.z = z;
201
220
  global.schemas = schemas;
202
- const result = safeEval(fixedCode, routePath);
221
+ const result = await safeEval(fixedCode, routePath2);
203
222
  delete global.schemas;
223
+ delete global[routeDefinerName];
224
+ delete global.z;
204
225
  return result;
205
226
  }
206
227
 
@@ -228,7 +249,7 @@ function verifyOptions(include, exclude) {
228
249
  import path3 from "path";
229
250
  function getRoutePathName(filePath, rootPath) {
230
251
  const dirName = path3.dirname(filePath);
231
- return "/" + path3.relative(rootPath, dirName).replaceAll("[", "{").replaceAll("]", "}").replaceAll("\\", "/").replace(/\([^)]+\)\/|\/\([^)]+\)|\([^)]+\)/g, "");
252
+ return "/" + path3.relative(rootPath, dirName).replaceAll("[", "{").replaceAll("]", "}").replaceAll("\\", "/");
232
253
  }
233
254
 
234
255
  // src/utils/deepEqual.ts
@@ -254,36 +275,38 @@ function deepEqual(a, b) {
254
275
  }
255
276
 
256
277
  // src/core/zod-to-openapi.ts
257
- import { zodToJsonSchema } from "zod-to-json-schema";
258
-
259
- // src/utils/zod-schema.ts
260
- function isFile(schema) {
261
- const file = new File([], "nothing.txt");
262
- const plainObject = { name: "test", size: 0 };
263
- const fileResult = schema.safeParse(file);
264
- const objectResult = schema.safeParse(plainObject);
265
- return fileResult.success && !objectResult.success;
266
- }
267
-
268
- // src/core/zod-to-openapi.ts
269
- function convertToOpenAPI(schema, isArray) {
270
- const result = zodToJsonSchema(isArray ? schema.array() : schema, {
271
- target: "openApi3",
272
- $refStrategy: "none"
273
- });
274
- if (result.type === "object" && result.properties) {
275
- for (const [propName, prop] of Object.entries(schema.shape)) {
276
- if (isFile(prop)) {
277
- result.properties[propName] = {
278
- type: "string",
279
- format: "binary",
280
- description: prop.description
281
- // contentEncoding: "base64", // swagger-ui-react doesn't support this
282
- };
283
- }
278
+ import { z as z2 } from "zod";
279
+ function fixSchema(schema) {
280
+ if ("unwrap" in schema && typeof schema.unwrap === "function") {
281
+ switch (schema._zod.def.type) {
282
+ case "nullable":
283
+ return fixSchema(schema.unwrap()).nullable();
284
+ case "optional":
285
+ return fixSchema(schema.unwrap()).optional();
286
+ case "readonly":
287
+ return fixSchema(schema.unwrap()).readonly();
288
+ case "array":
289
+ return fixSchema(schema.unwrap()).array();
290
+ case "nonoptional":
291
+ return fixSchema(schema.unwrap());
292
+ default:
293
+ throw new Error(`${schema._zod.def.type} type is not covered in fixSchema (@omer-x/next-openapi-json-generator")`);
284
294
  }
285
295
  }
286
- return result;
296
+ if (schema._zod.def.type === "date") {
297
+ return z2.iso.datetime();
298
+ }
299
+ if (schema._zod.def.type === "object") {
300
+ const { shape } = schema;
301
+ const entries = Object.entries(shape);
302
+ const alteredEntries = entries.map(([propName, prop]) => [propName, fixSchema(prop)]);
303
+ const newShape = Object.fromEntries(alteredEntries);
304
+ return z2.object(newShape);
305
+ }
306
+ return schema;
307
+ }
308
+ function convertToOpenAPI(schema, isArray) {
309
+ return z2.toJSONSchema(fixSchema(isArray ? schema.array() : schema));
287
310
  }
288
311
 
289
312
  // src/core/mask.ts
@@ -451,7 +474,7 @@ async function generateOpenApiSpec(schemas, {
451
474
  securitySchemes
452
475
  }
453
476
  };
454
- return {
477
+ return JSON.parse(JSON.stringify({
455
478
  openapi: "3.1.0",
456
479
  info: {
457
480
  title: metadata.serviceName,
@@ -462,7 +485,7 @@ async function generateOpenApiSpec(schemas, {
462
485
  ...clearUnusedSchemasOption ? clearUnusedSchemas(pathsAndComponents) : pathsAndComponents,
463
486
  security,
464
487
  tags: []
465
- };
488
+ }));
466
489
  }
467
490
 
468
491
  // src/index.ts
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@spikers/next-openapi-json-generator",
3
- "version": "1.1.2",
3
+ "version": "2.0.0",
4
4
  "description": "a Next.js plugin to generate OpenAPI documentation from route handlers",
5
5
  "keywords": [
6
6
  "next.js",
@@ -35,29 +35,32 @@
35
35
  }
36
36
  },
37
37
  "scripts": {
38
- "test": "jest",
38
+ "lint": "eslint --flag unstable_native_nodejs_ts_config",
39
+ "lint:fix": "eslint --fix --flag unstable_native_nodejs_ts_config",
40
+ "test": "vitest run --coverage",
41
+ "test:watch": "vitest --coverage",
39
42
  "dev": "tsup --watch",
40
43
  "build": "tsup"
41
44
  },
45
+ "peerDependencies": {
46
+ "@omer-x/json-schema-types": "^1",
47
+ "@omer-x/next-openapi-route-handler": "^2",
48
+ "@omer-x/openapi-types": "^1",
49
+ "typescript": "^5",
50
+ "zod": "^4"
51
+ },
42
52
  "dependencies": {
53
+ "@omer-x/openapi-optimizer": "alpha",
43
54
  "@omer-x/package-metadata": "^1.0.2",
44
- "minimatch": "^10.0.1",
45
- "typescript": "^5.7.2",
46
- "zod-to-json-schema": "^3.24.1"
55
+ "minimatch": "^10.1.1"
47
56
  },
48
57
  "devDependencies": {
49
- "@omer-x/eslint-config": "^2.1.2",
50
- "@types/node": "^22.10.2",
51
- "conventional-changelog-conventionalcommits": "^9.0.0",
52
- "eslint": "^9.16.0",
53
- "semantic-release": "^24.2.0",
54
- "ts-jest": "^29.2.5",
55
- "ts-node": "^10.9.2",
56
- "tsup": "^8.3.5",
57
- "zod": "^3.24.1"
58
- },
59
- "peerDependencies": {
60
- "@omer-x/next-openapi-route-handler": "^1",
61
- "@omer-x/openapi-types": "^1"
58
+ "@omer-x/eslint-config": "^2.2.6",
59
+ "@types/node": "^25.0.3",
60
+ "@vitest/coverage-v8": "^4.0.16",
61
+ "eslint": "^9.39.2",
62
+ "semantic-release": "^25.0.2",
63
+ "tsup": "^8.5.1",
64
+ "vitest": "^4.0.16"
62
65
  }
63
66
  }