next-openapi-gen 0.1.2 → 0.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/commands/generate.js +21 -21
- package/dist/commands/init.js +104 -104
- package/dist/components/rapidoc.js +4 -4
- package/dist/components/redoc.js +4 -4
- package/dist/components/stoplight.js +4 -4
- package/dist/components/swagger.js +8 -8
- package/dist/index.js +22 -22
- package/dist/lib/openapi-generator.js +34 -34
- package/dist/lib/route-processor.js +173 -145
- package/dist/lib/schema-processor.js +295 -250
- package/dist/lib/utils.js +88 -83
- package/dist/openapi-template.js +29 -29
- package/dist/types.js +1 -1
- package/package.json +1 -1
|
@@ -1,145 +1,173 @@
|
|
|
1
|
-
import * as t from "@babel/types";
|
|
2
|
-
import fs from "fs";
|
|
3
|
-
import path from "path";
|
|
4
|
-
import traverse from "@babel/traverse";
|
|
5
|
-
import { parse } from "@babel/parser";
|
|
6
|
-
import { SchemaProcessor } from "./schema-processor.js";
|
|
7
|
-
import { capitalize, extractJSDocComments, getOperationId } from "./utils.js";
|
|
8
|
-
const HTTP_METHODS = ["GET", "POST", "PUT", "PATCH", "DELETE"];
|
|
9
|
-
const MUTATION_HTTP_METHODS = ["PATCH", "POST", "PUT"];
|
|
10
|
-
export class RouteProcessor {
|
|
11
|
-
swaggerPaths = {};
|
|
12
|
-
schemaProcessor;
|
|
13
|
-
config;
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
.
|
|
136
|
-
.
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
1
|
+
import * as t from "@babel/types";
|
|
2
|
+
import fs from "fs";
|
|
3
|
+
import path from "path";
|
|
4
|
+
import traverse from "@babel/traverse";
|
|
5
|
+
import { parse } from "@babel/parser";
|
|
6
|
+
import { SchemaProcessor } from "./schema-processor.js";
|
|
7
|
+
import { capitalize, extractJSDocComments, getOperationId } from "./utils.js";
|
|
8
|
+
const HTTP_METHODS = ["GET", "POST", "PUT", "PATCH", "DELETE"];
|
|
9
|
+
const MUTATION_HTTP_METHODS = ["PATCH", "POST", "PUT"];
|
|
10
|
+
export class RouteProcessor {
|
|
11
|
+
swaggerPaths = {};
|
|
12
|
+
schemaProcessor;
|
|
13
|
+
config;
|
|
14
|
+
directoryCache = {};
|
|
15
|
+
statCache = {};
|
|
16
|
+
processFileTracker = {};
|
|
17
|
+
constructor(config) {
|
|
18
|
+
this.config = config;
|
|
19
|
+
this.schemaProcessor = new SchemaProcessor(config.schemaDir);
|
|
20
|
+
}
|
|
21
|
+
isRoute(varName) {
|
|
22
|
+
return HTTP_METHODS.includes(varName);
|
|
23
|
+
}
|
|
24
|
+
processFile(filePath) {
|
|
25
|
+
// Check if the file has already been processed
|
|
26
|
+
if (this.processFileTracker[filePath])
|
|
27
|
+
return;
|
|
28
|
+
const content = fs.readFileSync(filePath, "utf-8");
|
|
29
|
+
const ast = parse(content, {
|
|
30
|
+
sourceType: "module",
|
|
31
|
+
plugins: ["typescript", "decorators-legacy"],
|
|
32
|
+
});
|
|
33
|
+
traverse.default(ast, {
|
|
34
|
+
ExportNamedDeclaration: (path) => {
|
|
35
|
+
const declaration = path.node.declaration;
|
|
36
|
+
if (t.isFunctionDeclaration(declaration) &&
|
|
37
|
+
t.isIdentifier(declaration.id)) {
|
|
38
|
+
const dataTypes = extractJSDocComments(path);
|
|
39
|
+
if (this.isRoute(declaration.id.name)) {
|
|
40
|
+
// Don't bother adding routes for processing if only including OpenAPI routes and the route is not OpenAPI
|
|
41
|
+
if (!this.config.includeOpenApiRoutes || (this.config.includeOpenApiRoutes && dataTypes.isOpenApi))
|
|
42
|
+
this.addRouteToPaths(declaration.id.name, filePath, dataTypes);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
if (t.isVariableDeclaration(declaration)) {
|
|
46
|
+
declaration.declarations.forEach((decl) => {
|
|
47
|
+
if (t.isVariableDeclarator(decl) && t.isIdentifier(decl.id)) {
|
|
48
|
+
if (this.isRoute(decl.id.name)) {
|
|
49
|
+
const dataTypes = extractJSDocComments(path);
|
|
50
|
+
// Don't bother adding routes for processing if only including OpenAPI routes and the route is not OpenAPI
|
|
51
|
+
if (!this.config.includeOpenApiRoutes || (this.config.includeOpenApiRoutes && dataTypes.isOpenApi))
|
|
52
|
+
this.addRouteToPaths(decl.id.name, filePath, dataTypes);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
},
|
|
58
|
+
});
|
|
59
|
+
this.processFileTracker[filePath] = true;
|
|
60
|
+
}
|
|
61
|
+
scanApiRoutes(dir) {
|
|
62
|
+
let files = this.directoryCache[dir];
|
|
63
|
+
if (!files) {
|
|
64
|
+
files = fs.readdirSync(dir);
|
|
65
|
+
this.directoryCache[dir] = files;
|
|
66
|
+
}
|
|
67
|
+
files.forEach((file) => {
|
|
68
|
+
const filePath = path.join(dir, file);
|
|
69
|
+
let stat = this.statCache[filePath];
|
|
70
|
+
if (!stat) {
|
|
71
|
+
stat = fs.statSync(filePath);
|
|
72
|
+
this.statCache[filePath] = stat;
|
|
73
|
+
}
|
|
74
|
+
if (stat.isDirectory()) {
|
|
75
|
+
this.scanApiRoutes(filePath);
|
|
76
|
+
// @ts-ignore
|
|
77
|
+
}
|
|
78
|
+
else if (file.endsWith(".ts")) {
|
|
79
|
+
this.processFile(filePath);
|
|
80
|
+
}
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
addRouteToPaths(varName, filePath, dataTypes) {
|
|
84
|
+
const method = varName.toLowerCase();
|
|
85
|
+
const routePath = this.getRoutePath(filePath);
|
|
86
|
+
const rootPath = capitalize(routePath.split("/")[1]);
|
|
87
|
+
const operationId = getOperationId(routePath, method);
|
|
88
|
+
const { summary, description, auth, isOpenApi } = dataTypes;
|
|
89
|
+
if (this.config.includeOpenApiRoutes && !isOpenApi) {
|
|
90
|
+
// If flag is enabled and there is no @openapi tag, then skip path
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
if (!this.swaggerPaths[routePath]) {
|
|
94
|
+
this.swaggerPaths[routePath] = {};
|
|
95
|
+
}
|
|
96
|
+
const { params, pathParams, body, responses } = this.schemaProcessor.getSchemaContent(dataTypes);
|
|
97
|
+
const definition = {
|
|
98
|
+
operationId: operationId,
|
|
99
|
+
summary: summary,
|
|
100
|
+
description: description,
|
|
101
|
+
tags: [rootPath],
|
|
102
|
+
};
|
|
103
|
+
// Add auth
|
|
104
|
+
if (auth) {
|
|
105
|
+
definition.security = [
|
|
106
|
+
{
|
|
107
|
+
[auth]: [],
|
|
108
|
+
},
|
|
109
|
+
];
|
|
110
|
+
}
|
|
111
|
+
definition.parameters = [];
|
|
112
|
+
if (params) {
|
|
113
|
+
definition.parameters =
|
|
114
|
+
this.schemaProcessor.createRequestParamsSchema(params);
|
|
115
|
+
}
|
|
116
|
+
if (pathParams) {
|
|
117
|
+
const moreParams = this.schemaProcessor.createRequestParamsSchema(pathParams, true);
|
|
118
|
+
definition.parameters.push(...moreParams);
|
|
119
|
+
}
|
|
120
|
+
// Add request body
|
|
121
|
+
if (MUTATION_HTTP_METHODS.includes(method.toUpperCase())) {
|
|
122
|
+
definition.requestBody =
|
|
123
|
+
this.schemaProcessor.createRequestBodySchema(body);
|
|
124
|
+
}
|
|
125
|
+
// Add responses
|
|
126
|
+
definition.responses = responses
|
|
127
|
+
? this.schemaProcessor.createResponseSchema(responses)
|
|
128
|
+
: {};
|
|
129
|
+
this.swaggerPaths[routePath][method] = definition;
|
|
130
|
+
}
|
|
131
|
+
getRoutePath(filePath) {
|
|
132
|
+
const suffixPath = filePath.split("api")[1];
|
|
133
|
+
return suffixPath
|
|
134
|
+
.replace("route.ts", "")
|
|
135
|
+
.replaceAll("\\", "/")
|
|
136
|
+
.replace(/\/$/, "")
|
|
137
|
+
// Turns NextJS-style dynamic routes into OpenAPI-style dynamic routes
|
|
138
|
+
.replaceAll("[", "{")
|
|
139
|
+
.replaceAll("]", "}");
|
|
140
|
+
}
|
|
141
|
+
getSortedPaths(paths) {
|
|
142
|
+
function comparePaths(a, b) {
|
|
143
|
+
const aMethods = this.swaggerPaths[a] || {};
|
|
144
|
+
const bMethods = this.swaggerPaths[b] || {};
|
|
145
|
+
// Extract tags for all methods in path a
|
|
146
|
+
const aTags = Object.values(aMethods).flatMap((method) => method.tags || []);
|
|
147
|
+
// Extract tags for all methods in path b
|
|
148
|
+
const bTags = Object.values(bMethods).flatMap((method) => method.tags || []);
|
|
149
|
+
// Let's user only the first tags
|
|
150
|
+
const aPrimaryTag = aTags[0] || "";
|
|
151
|
+
const bPrimaryTag = bTags[0] || "";
|
|
152
|
+
// Sort alphabetically based on the first tag
|
|
153
|
+
const tagComparison = aPrimaryTag.localeCompare(bPrimaryTag);
|
|
154
|
+
if (tagComparison !== 0) {
|
|
155
|
+
return tagComparison; // Return the result of tag comparison
|
|
156
|
+
}
|
|
157
|
+
// Compare lengths of the paths
|
|
158
|
+
const aLength = a.split("/").length;
|
|
159
|
+
const bLength = b.split("/").length;
|
|
160
|
+
return aLength - bLength; // Shorter paths come before longer ones
|
|
161
|
+
}
|
|
162
|
+
return Object.keys(paths)
|
|
163
|
+
.sort(comparePaths.bind(this))
|
|
164
|
+
.reduce((sorted, key) => {
|
|
165
|
+
sorted[key] = paths[key];
|
|
166
|
+
return sorted;
|
|
167
|
+
}, {});
|
|
168
|
+
}
|
|
169
|
+
getSwaggerPaths() {
|
|
170
|
+
const paths = this.getSortedPaths(this.swaggerPaths);
|
|
171
|
+
return this.getSortedPaths(paths);
|
|
172
|
+
}
|
|
173
|
+
}
|