next-openapi-gen 0.7.2 → 0.7.4
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/README.md +15 -13
- package/dist/commands/generate.js +2 -2
- package/dist/lib/openapi-generator.js +2 -1
- package/dist/lib/route-processor.js +2 -6
- package/dist/lib/schema-processor.js +16 -12
- package/dist/lib/utils.js +20 -0
- package/dist/lib/zod-converter.js +5 -17
- package/dist/openapi-template.js +1 -0
- package/package.json +9 -9
package/README.md
CHANGED
|
@@ -60,6 +60,7 @@ During initialization (`npx next-openapi-gen init`), a configuration file `next.
|
|
|
60
60
|
"schemaDir": "src/types", // or "src/schemas" for Zod schemas
|
|
61
61
|
"schemaType": "zod", // or "typescript" for TypeScript types
|
|
62
62
|
"outputFile": "openapi.json",
|
|
63
|
+
"outputDir": "./public",
|
|
63
64
|
"docsUrl": "/api-docs",
|
|
64
65
|
"includeOpenApiRoutes": false,
|
|
65
66
|
"debug": false
|
|
@@ -68,18 +69,19 @@ During initialization (`npx next-openapi-gen init`), a configuration file `next.
|
|
|
68
69
|
|
|
69
70
|
### Configuration Options
|
|
70
71
|
|
|
71
|
-
| Option | Description
|
|
72
|
-
| ---------------------- |
|
|
73
|
-
| `apiDir` | Path to the API directory
|
|
74
|
-
| `schemaDir` | Path to the types/schemas directory
|
|
75
|
-
| `schemaType` | Schema type: `"zod"` or `"typescript"`
|
|
76
|
-
| `outputFile` |
|
|
77
|
-
| `
|
|
78
|
-
| `
|
|
79
|
-
| `
|
|
80
|
-
| `
|
|
81
|
-
| `
|
|
82
|
-
| `
|
|
72
|
+
| Option | Description |
|
|
73
|
+
| ---------------------- | ---------------------------------------------------------------------- |
|
|
74
|
+
| `apiDir` | Path to the API directory |
|
|
75
|
+
| `schemaDir` | Path to the types/schemas directory |
|
|
76
|
+
| `schemaType` | Schema type: `"zod"` or `"typescript"` |
|
|
77
|
+
| `outputFile` | Name of the OpenAPI output file |
|
|
78
|
+
| `outputDir` | Directory where OpenAPI file will be generated (default: `"./public"`) |
|
|
79
|
+
| `docsUrl` | API documentation URL (for Swagger UI) |
|
|
80
|
+
| `includeOpenApiRoutes` | Whether to include only routes with @openapi tag |
|
|
81
|
+
| `defaultResponseSet` | Default error response set for all endpoints |
|
|
82
|
+
| `responseSets` | Named sets of error response codes |
|
|
83
|
+
| `errorConfig` | Error schema configuration |
|
|
84
|
+
| `debug` | Enable detailed logging during generation |
|
|
83
85
|
|
|
84
86
|
## Documenting Your API
|
|
85
87
|
|
|
@@ -192,7 +194,7 @@ This command will generate OpenAPI documentation based on your API code:
|
|
|
192
194
|
|
|
193
195
|
- Scan API directories for routes
|
|
194
196
|
- Analyze types/schemas
|
|
195
|
-
- Generate OpenAPI file (`openapi.json`) in `public` folder
|
|
197
|
+
- Generate OpenAPI file (`openapi.json`) in specified output directory (default: `public` folder)
|
|
196
198
|
- Create Scalar/Swagger UI endpoint and page (if enabled)
|
|
197
199
|
|
|
198
200
|
### 3. View API Documentation
|
|
@@ -10,8 +10,8 @@ export async function generate() {
|
|
|
10
10
|
// Create api dir if not exists
|
|
11
11
|
const apiDir = path.resolve(config.apiDir);
|
|
12
12
|
await fse.ensureDir(apiDir);
|
|
13
|
-
//
|
|
14
|
-
const outputDir = path.resolve(
|
|
13
|
+
// Use user-defined output directory
|
|
14
|
+
const outputDir = path.resolve(config.outputDir);
|
|
15
15
|
await fse.ensureDir(outputDir);
|
|
16
16
|
const apiDocs = generator.generate();
|
|
17
17
|
// Write api docs
|
|
@@ -17,13 +17,14 @@ export class OpenApiGenerator {
|
|
|
17
17
|
}
|
|
18
18
|
getConfig() {
|
|
19
19
|
// @ts-ignore
|
|
20
|
-
const { apiDir, schemaDir, docsUrl, ui, outputFile, includeOpenApiRoutes, schemaType = "typescript", defaultResponseSet, responseSets, errorConfig, debug } = this.template;
|
|
20
|
+
const { apiDir, schemaDir, docsUrl, ui, outputFile, outputDir, includeOpenApiRoutes, schemaType = "typescript", defaultResponseSet, responseSets, errorConfig, debug } = this.template;
|
|
21
21
|
return {
|
|
22
22
|
apiDir,
|
|
23
23
|
schemaDir,
|
|
24
24
|
docsUrl,
|
|
25
25
|
ui,
|
|
26
26
|
outputFile,
|
|
27
|
+
outputDir,
|
|
27
28
|
includeOpenApiRoutes,
|
|
28
29
|
schemaType,
|
|
29
30
|
defaultResponseSet,
|
|
@@ -2,9 +2,8 @@ import * as t from "@babel/types";
|
|
|
2
2
|
import fs from "fs";
|
|
3
3
|
import path from "path";
|
|
4
4
|
import traverse from "@babel/traverse";
|
|
5
|
-
import { parse } from "@babel/parser";
|
|
6
5
|
import { SchemaProcessor } from "./schema-processor.js";
|
|
7
|
-
import { capitalize, extractJSDocComments, extractPathParameters, getOperationId, } from "./utils.js";
|
|
6
|
+
import { capitalize, extractJSDocComments, parseTypeScriptFile, extractPathParameters, getOperationId, } from "./utils.js";
|
|
8
7
|
import { logger } from "./logger.js";
|
|
9
8
|
const HTTP_METHODS = ["GET", "POST", "PUT", "PATCH", "DELETE"];
|
|
10
9
|
const MUTATION_HTTP_METHODS = ["PATCH", "POST", "PUT"];
|
|
@@ -118,10 +117,7 @@ export class RouteProcessor {
|
|
|
118
117
|
if (this.processFileTracker[filePath])
|
|
119
118
|
return;
|
|
120
119
|
const content = fs.readFileSync(filePath, "utf-8");
|
|
121
|
-
const ast =
|
|
122
|
-
sourceType: "module",
|
|
123
|
-
plugins: ["typescript", "decorators-legacy"],
|
|
124
|
-
});
|
|
120
|
+
const ast = parseTypeScriptFile(content);
|
|
125
121
|
traverse.default(ast, {
|
|
126
122
|
ExportNamedDeclaration: (path) => {
|
|
127
123
|
const declaration = path.node.declaration;
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import fs from "fs";
|
|
2
2
|
import path from "path";
|
|
3
|
-
import { parse } from "@babel/parser";
|
|
4
3
|
import traverse from "@babel/traverse";
|
|
5
4
|
import * as t from "@babel/types";
|
|
5
|
+
import { parseTypeScriptFile } from "./utils.js";
|
|
6
6
|
import { ZodSchemaConverter } from "./zod-converter.js";
|
|
7
7
|
import { logger } from "./logger.js";
|
|
8
8
|
export class SchemaProcessor {
|
|
@@ -167,10 +167,14 @@ export class SchemaProcessor {
|
|
|
167
167
|
const enumValues = this.processEnum(typeNode);
|
|
168
168
|
return enumValues;
|
|
169
169
|
}
|
|
170
|
-
if (t.isTSTypeLiteral(typeNode) ||
|
|
170
|
+
if (t.isTSTypeLiteral(typeNode) ||
|
|
171
|
+
t.isTSInterfaceBody(typeNode) ||
|
|
172
|
+
t.isTSInterfaceDeclaration(typeNode)) {
|
|
171
173
|
const properties = {};
|
|
172
174
|
// Handle interface extends clause
|
|
173
|
-
if (t.isTSInterfaceDeclaration(typeNode) &&
|
|
175
|
+
if (t.isTSInterfaceDeclaration(typeNode) &&
|
|
176
|
+
typeNode.extends &&
|
|
177
|
+
typeNode.extends.length > 0) {
|
|
174
178
|
typeNode.extends.forEach((extendedType) => {
|
|
175
179
|
const extendedSchema = this.resolveTSNodeType(extendedType);
|
|
176
180
|
if (extendedSchema.properties) {
|
|
@@ -179,7 +183,9 @@ export class SchemaProcessor {
|
|
|
179
183
|
});
|
|
180
184
|
}
|
|
181
185
|
// Get members from interface declaration body or direct members
|
|
182
|
-
const members = t.isTSInterfaceDeclaration(typeNode)
|
|
186
|
+
const members = t.isTSInterfaceDeclaration(typeNode)
|
|
187
|
+
? typeNode.body.body
|
|
188
|
+
: typeNode.members;
|
|
183
189
|
if (members) {
|
|
184
190
|
(members || []).forEach((member) => {
|
|
185
191
|
if (t.isTSPropertySignature(member) && t.isIdentifier(member.key)) {
|
|
@@ -270,9 +276,9 @@ export class SchemaProcessor {
|
|
|
270
276
|
if (t.isIdentifier(node.expression)) {
|
|
271
277
|
// Convert to TSTypeReference-like structure for processing
|
|
272
278
|
const syntheticNode = {
|
|
273
|
-
type:
|
|
279
|
+
type: "TSTypeReference",
|
|
274
280
|
typeName: node.expression,
|
|
275
|
-
typeParameters: node.typeParameters
|
|
281
|
+
typeParameters: node.typeParameters,
|
|
276
282
|
};
|
|
277
283
|
return this.resolveTSNodeType(syntheticNode);
|
|
278
284
|
}
|
|
@@ -322,13 +328,14 @@ export class SchemaProcessor {
|
|
|
322
328
|
const properties = {};
|
|
323
329
|
const keyNames = this.extractKeysFromLiteralType(keysParam);
|
|
324
330
|
if (typeName === "Pick") {
|
|
325
|
-
keyNames.forEach(key => {
|
|
331
|
+
keyNames.forEach((key) => {
|
|
326
332
|
if (baseType.properties[key]) {
|
|
327
333
|
properties[key] = baseType.properties[key];
|
|
328
334
|
}
|
|
329
335
|
});
|
|
330
336
|
}
|
|
331
|
-
else {
|
|
337
|
+
else {
|
|
338
|
+
// Omit
|
|
332
339
|
Object.entries(baseType.properties).forEach(([key, value]) => {
|
|
333
340
|
if (!keyNames.includes(key)) {
|
|
334
341
|
properties[key] = value;
|
|
@@ -460,10 +467,7 @@ export class SchemaProcessor {
|
|
|
460
467
|
try {
|
|
461
468
|
// Recognizes different elements of TS like variable, type, interface, enum
|
|
462
469
|
const content = fs.readFileSync(filePath, "utf-8");
|
|
463
|
-
const ast =
|
|
464
|
-
sourceType: "module",
|
|
465
|
-
plugins: ["typescript", "decorators-legacy"],
|
|
466
|
-
});
|
|
470
|
+
const ast = parseTypeScriptFile(content);
|
|
467
471
|
this.collectTypeDefinitions(ast, schemaName);
|
|
468
472
|
// Reset the set of processed types before each schema processing
|
|
469
473
|
this.processingTypes.clear();
|
package/dist/lib/utils.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { parse } from "@babel/parser";
|
|
1
2
|
export function capitalize(string) {
|
|
2
3
|
return string.charAt(0).toUpperCase() + string.slice(1);
|
|
3
4
|
}
|
|
@@ -208,3 +209,22 @@ export function getOperationId(routePath, method) {
|
|
|
208
209
|
const operation = routePath.replaceAll(/\//g, "-").replace(/^-/, "");
|
|
209
210
|
return `${method}-${operation}`;
|
|
210
211
|
}
|
|
212
|
+
/**
|
|
213
|
+
* Common Babel parser configuration for TypeScript files with JSX support
|
|
214
|
+
*/
|
|
215
|
+
const DEFAULT_PARSER_OPTIONS = {
|
|
216
|
+
sourceType: "module",
|
|
217
|
+
plugins: ["typescript", "jsx", "decorators-legacy"],
|
|
218
|
+
};
|
|
219
|
+
/**
|
|
220
|
+
* Parse TypeScript/TSX file content with the standard configuration
|
|
221
|
+
* @param content - File content to parse
|
|
222
|
+
* @param options - Optional parser options to override defaults
|
|
223
|
+
* @returns Parsed AST
|
|
224
|
+
*/
|
|
225
|
+
export function parseTypeScriptFile(content, options) {
|
|
226
|
+
return parse(content, {
|
|
227
|
+
...DEFAULT_PARSER_OPTIONS,
|
|
228
|
+
...options,
|
|
229
|
+
});
|
|
230
|
+
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import fs from "fs";
|
|
2
2
|
import path from "path";
|
|
3
|
-
import { parse } from "@babel/parser";
|
|
4
3
|
import traverse from "@babel/traverse";
|
|
5
4
|
import * as t from "@babel/types";
|
|
5
|
+
import { parseTypeScriptFile } from "./utils.js";
|
|
6
6
|
import { logger } from "./logger.js";
|
|
7
7
|
/**
|
|
8
8
|
* Class for converting Zod schemas to OpenAPI specifications
|
|
@@ -146,10 +146,7 @@ export class ZodSchemaConverter {
|
|
|
146
146
|
return;
|
|
147
147
|
}
|
|
148
148
|
// Parse the file
|
|
149
|
-
const ast =
|
|
150
|
-
sourceType: "module",
|
|
151
|
-
plugins: ["typescript", "decorators-legacy"],
|
|
152
|
-
});
|
|
149
|
+
const ast = parseTypeScriptFile(content);
|
|
153
150
|
// Create a map to store imported modules
|
|
154
151
|
const importedModules = {};
|
|
155
152
|
// Look for all exported Zod schemas
|
|
@@ -459,10 +456,7 @@ export class ZodSchemaConverter {
|
|
|
459
456
|
processAllSchemasInFile(filePath) {
|
|
460
457
|
try {
|
|
461
458
|
const content = fs.readFileSync(filePath, "utf-8");
|
|
462
|
-
const ast =
|
|
463
|
-
sourceType: "module",
|
|
464
|
-
plugins: ["typescript", "decorators-legacy"],
|
|
465
|
-
});
|
|
459
|
+
const ast = parseTypeScriptFile(content);
|
|
466
460
|
traverse.default(ast, {
|
|
467
461
|
ExportNamedDeclaration: (path) => {
|
|
468
462
|
if (t.isVariableDeclaration(path.node.declaration)) {
|
|
@@ -1343,10 +1337,7 @@ export class ZodSchemaConverter {
|
|
|
1343
1337
|
scanFileForTypeMappings(filePath) {
|
|
1344
1338
|
try {
|
|
1345
1339
|
const content = fs.readFileSync(filePath, "utf-8");
|
|
1346
|
-
const ast =
|
|
1347
|
-
sourceType: "module",
|
|
1348
|
-
plugins: ["typescript", "decorators-legacy"],
|
|
1349
|
-
});
|
|
1340
|
+
const ast = parseTypeScriptFile(content);
|
|
1350
1341
|
traverse.default(ast, {
|
|
1351
1342
|
TSTypeAliasDeclaration: (path) => {
|
|
1352
1343
|
if (t.isIdentifier(path.node.id)) {
|
|
@@ -1413,10 +1404,7 @@ export class ZodSchemaConverter {
|
|
|
1413
1404
|
preprocessAllSchemasInFile(filePath) {
|
|
1414
1405
|
try {
|
|
1415
1406
|
const content = fs.readFileSync(filePath, "utf-8");
|
|
1416
|
-
const ast =
|
|
1417
|
-
sourceType: "module",
|
|
1418
|
-
plugins: ["typescript", "decorators-legacy"],
|
|
1419
|
-
});
|
|
1407
|
+
const ast = parseTypeScriptFile(content);
|
|
1420
1408
|
// Collect all exported Zod schemas
|
|
1421
1409
|
traverse.default(ast, {
|
|
1422
1410
|
ExportNamedDeclaration: (path) => {
|
package/dist/openapi-template.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "next-openapi-gen",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.4",
|
|
4
4
|
"description": "Automatically generate OpenAPI 3.0 documentation from Next.js projects, with support for Zod schemas and TypeScript types.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -42,15 +42,15 @@
|
|
|
42
42
|
"node": ">=18.0.0"
|
|
43
43
|
},
|
|
44
44
|
"dependencies": {
|
|
45
|
-
"@babel/parser": "^7.
|
|
46
|
-
"@babel/traverse": "^7.
|
|
47
|
-
"@babel/types": "^7.
|
|
48
|
-
"commander": "^
|
|
49
|
-
"fs-extra": "^
|
|
50
|
-
"ora": "^8.
|
|
45
|
+
"@babel/parser": "^7.28.3",
|
|
46
|
+
"@babel/traverse": "^7.28.3",
|
|
47
|
+
"@babel/types": "^7.28.2",
|
|
48
|
+
"commander": "^14.0.0",
|
|
49
|
+
"fs-extra": "^11.3.1",
|
|
50
|
+
"ora": "^8.2.0"
|
|
51
51
|
},
|
|
52
52
|
"devDependencies": {
|
|
53
|
-
"@types/node": "^
|
|
54
|
-
"typescript": "^
|
|
53
|
+
"@types/node": "^24.3.0",
|
|
54
|
+
"typescript": "^5.9.2"
|
|
55
55
|
}
|
|
56
56
|
}
|