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,250 +1,295 @@
|
|
|
1
|
-
import fs from "fs";
|
|
2
|
-
import path from "path";
|
|
3
|
-
import { parse } from "@babel/parser";
|
|
4
|
-
import traverse from "@babel/traverse";
|
|
5
|
-
import * as t from "@babel/types";
|
|
6
|
-
export class SchemaProcessor {
|
|
7
|
-
schemaDir;
|
|
8
|
-
typeDefinitions = {};
|
|
9
|
-
openapiDefinitions = {};
|
|
10
|
-
contentType = "";
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
}
|
|
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
|
-
if (t.
|
|
99
|
-
return {
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
return
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
if (t.
|
|
125
|
-
return {
|
|
126
|
-
|
|
127
|
-
};
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
const
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
const
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
return
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
1
|
+
import fs from "fs";
|
|
2
|
+
import path from "path";
|
|
3
|
+
import { parse } from "@babel/parser";
|
|
4
|
+
import traverse from "@babel/traverse";
|
|
5
|
+
import * as t from "@babel/types";
|
|
6
|
+
export class SchemaProcessor {
|
|
7
|
+
schemaDir;
|
|
8
|
+
typeDefinitions = {};
|
|
9
|
+
openapiDefinitions = {};
|
|
10
|
+
contentType = "";
|
|
11
|
+
directoryCache = {};
|
|
12
|
+
statCache = {};
|
|
13
|
+
processSchemaTracker = {};
|
|
14
|
+
constructor(schemaDir) {
|
|
15
|
+
this.schemaDir = path.resolve(schemaDir);
|
|
16
|
+
}
|
|
17
|
+
findSchemaDefinition(schemaName, contentType) {
|
|
18
|
+
let schemaNode = null;
|
|
19
|
+
// assign type that is actually processed
|
|
20
|
+
this.contentType = contentType;
|
|
21
|
+
this.scanSchemaDir(this.schemaDir, schemaName);
|
|
22
|
+
return schemaNode;
|
|
23
|
+
}
|
|
24
|
+
scanSchemaDir(dir, schemaName) {
|
|
25
|
+
let files = this.directoryCache[dir];
|
|
26
|
+
if (typeof files === "undefined") {
|
|
27
|
+
files = fs.readdirSync(dir);
|
|
28
|
+
this.directoryCache[dir] = files;
|
|
29
|
+
}
|
|
30
|
+
files.forEach((file) => {
|
|
31
|
+
const filePath = path.join(dir, file);
|
|
32
|
+
let stat = this.statCache[filePath];
|
|
33
|
+
if (typeof stat === "undefined") {
|
|
34
|
+
stat = fs.statSync(filePath);
|
|
35
|
+
this.statCache[filePath] = stat;
|
|
36
|
+
}
|
|
37
|
+
if (stat.isDirectory()) {
|
|
38
|
+
this.scanSchemaDir(filePath, schemaName);
|
|
39
|
+
}
|
|
40
|
+
else if (file.endsWith(".ts")) {
|
|
41
|
+
this.processSchemaFile(filePath, schemaName);
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
collectTypeDefinitions(ast, schemaName) {
|
|
46
|
+
traverse.default(ast, {
|
|
47
|
+
VariableDeclarator: (path) => {
|
|
48
|
+
if (t.isIdentifier(path.node.id, { name: schemaName })) {
|
|
49
|
+
const name = path.node.id.name;
|
|
50
|
+
this.typeDefinitions[name] = path.node.init || path.node;
|
|
51
|
+
}
|
|
52
|
+
},
|
|
53
|
+
TSTypeAliasDeclaration: (path) => {
|
|
54
|
+
if (t.isIdentifier(path.node.id, { name: schemaName })) {
|
|
55
|
+
const name = path.node.id.name;
|
|
56
|
+
this.typeDefinitions[name] = path.node.typeAnnotation;
|
|
57
|
+
}
|
|
58
|
+
},
|
|
59
|
+
TSInterfaceDeclaration: (path) => {
|
|
60
|
+
if (t.isIdentifier(path.node.id, { name: schemaName })) {
|
|
61
|
+
const name = path.node.id.name;
|
|
62
|
+
this.typeDefinitions[name] = path.node;
|
|
63
|
+
}
|
|
64
|
+
},
|
|
65
|
+
TSEnumDeclaration: (path) => {
|
|
66
|
+
if (t.isIdentifier(path.node.id, { name: schemaName })) {
|
|
67
|
+
const name = path.node.id.name;
|
|
68
|
+
this.typeDefinitions[name] = path.node;
|
|
69
|
+
}
|
|
70
|
+
},
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
resolveType(typeName) {
|
|
74
|
+
const typeNode = this.typeDefinitions[typeName.toString()];
|
|
75
|
+
if (!typeNode)
|
|
76
|
+
return {};
|
|
77
|
+
if (t.isTSEnumDeclaration(typeNode)) {
|
|
78
|
+
const enumValues = this.processEnum(typeNode);
|
|
79
|
+
return enumValues;
|
|
80
|
+
}
|
|
81
|
+
if (t.isTSTypeLiteral(typeNode) || t.isTSInterfaceBody(typeNode)) {
|
|
82
|
+
const properties = {};
|
|
83
|
+
if ("members" in typeNode) {
|
|
84
|
+
(typeNode.members || []).forEach((member) => {
|
|
85
|
+
if (t.isTSPropertySignature(member) && t.isIdentifier(member.key)) {
|
|
86
|
+
const propName = member.key.name;
|
|
87
|
+
const options = this.getPropertyOptions(member);
|
|
88
|
+
const property = {
|
|
89
|
+
...this.resolveTSNodeType(member.typeAnnotation?.typeAnnotation),
|
|
90
|
+
...options,
|
|
91
|
+
};
|
|
92
|
+
properties[propName] = property;
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
return { type: "object", properties };
|
|
97
|
+
}
|
|
98
|
+
if (t.isTSArrayType(typeNode)) {
|
|
99
|
+
return {
|
|
100
|
+
type: "array",
|
|
101
|
+
items: this.resolveTSNodeType(typeNode.elementType),
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
return {};
|
|
105
|
+
}
|
|
106
|
+
isDateString(node) {
|
|
107
|
+
if (t.isStringLiteral(node)) {
|
|
108
|
+
const dateRegex = /^\d{4}-\d{2}-\d{2}(T\d{2}:\d{2}:\d{2}(\.\d{3})?Z)?$/;
|
|
109
|
+
return dateRegex.test(node.value);
|
|
110
|
+
}
|
|
111
|
+
return false;
|
|
112
|
+
}
|
|
113
|
+
isDateObject(node) {
|
|
114
|
+
return t.isNewExpression(node) && t.isIdentifier(node.callee, { name: "Date" });
|
|
115
|
+
}
|
|
116
|
+
isDateNode(node) {
|
|
117
|
+
return this.isDateString(node) || this.isDateObject(node);
|
|
118
|
+
}
|
|
119
|
+
resolveTSNodeType(node) {
|
|
120
|
+
if (t.isTSStringKeyword(node))
|
|
121
|
+
return { type: "string" };
|
|
122
|
+
if (t.isTSNumberKeyword(node))
|
|
123
|
+
return { type: "number" };
|
|
124
|
+
if (t.isTSBooleanKeyword(node))
|
|
125
|
+
return { type: "boolean" };
|
|
126
|
+
if (this.isDateNode(node))
|
|
127
|
+
return { type: "Date" };
|
|
128
|
+
if (t.isTSTypeReference(node) && t.isIdentifier(node.typeName)) {
|
|
129
|
+
const typeName = node.typeName.name;
|
|
130
|
+
// Find type definition
|
|
131
|
+
this.findSchemaDefinition(typeName, this.contentType);
|
|
132
|
+
return this.resolveType(node.typeName.name);
|
|
133
|
+
}
|
|
134
|
+
if (t.isTSArrayType(node)) {
|
|
135
|
+
return {
|
|
136
|
+
type: "array",
|
|
137
|
+
items: this.resolveTSNodeType(node.elementType),
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
if (t.isTSTypeLiteral(node)) {
|
|
141
|
+
const properties = {};
|
|
142
|
+
node.members.forEach((member) => {
|
|
143
|
+
if (t.isTSPropertySignature(member) && t.isIdentifier(member.key)) {
|
|
144
|
+
const propName = member.key.name;
|
|
145
|
+
properties[propName] = this.resolveTSNodeType(member.typeAnnotation?.typeAnnotation);
|
|
146
|
+
}
|
|
147
|
+
});
|
|
148
|
+
return { type: "object", properties };
|
|
149
|
+
}
|
|
150
|
+
if (t.isTSUnionType(node)) {
|
|
151
|
+
return {
|
|
152
|
+
anyOf: node.types.map((subNode) => this.resolveTSNodeType(subNode)),
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
// case where a type is a reference to another defined type
|
|
156
|
+
if (t.isTSTypeReference(node) && t.isIdentifier(node.typeName)) {
|
|
157
|
+
return { $ref: `#/components/schemas/${node.typeName.name}` };
|
|
158
|
+
}
|
|
159
|
+
console.warn("Unrecognized TypeScript type node:", node);
|
|
160
|
+
return {};
|
|
161
|
+
}
|
|
162
|
+
processSchemaFile(filePath, schemaName) {
|
|
163
|
+
// Check if the file has already been processed
|
|
164
|
+
if (this.processSchemaTracker[`${filePath}-${schemaName}`])
|
|
165
|
+
return;
|
|
166
|
+
// Recognizes different elements of TS like variable, type, interface, enum
|
|
167
|
+
const content = fs.readFileSync(filePath, "utf-8");
|
|
168
|
+
const ast = parse(content, {
|
|
169
|
+
sourceType: "module",
|
|
170
|
+
plugins: ["typescript", "decorators-legacy"],
|
|
171
|
+
});
|
|
172
|
+
this.collectTypeDefinitions(ast, schemaName);
|
|
173
|
+
const definition = this.resolveType(schemaName);
|
|
174
|
+
this.openapiDefinitions[schemaName] = definition;
|
|
175
|
+
this.processSchemaTracker[`${filePath}-${schemaName}`] = true;
|
|
176
|
+
return definition;
|
|
177
|
+
}
|
|
178
|
+
processEnum(enumNode) {
|
|
179
|
+
// Initialization OpenAPI enum object
|
|
180
|
+
const enumSchema = {
|
|
181
|
+
type: "string",
|
|
182
|
+
enum: [],
|
|
183
|
+
};
|
|
184
|
+
// Iterate throught enum members
|
|
185
|
+
enumNode.members.forEach((member) => {
|
|
186
|
+
if (t.isTSEnumMember(member)) {
|
|
187
|
+
// @ts-ignore
|
|
188
|
+
const name = member.id?.name;
|
|
189
|
+
// @ts-ignore
|
|
190
|
+
const value = member.initializer?.value;
|
|
191
|
+
let type = member.initializer?.type;
|
|
192
|
+
if (type === "NumericLiteral") {
|
|
193
|
+
enumSchema.type = "number";
|
|
194
|
+
}
|
|
195
|
+
const targetValue = value || name;
|
|
196
|
+
enumSchema.enum.push(targetValue);
|
|
197
|
+
}
|
|
198
|
+
});
|
|
199
|
+
return enumSchema;
|
|
200
|
+
}
|
|
201
|
+
getPropertyOptions(node) {
|
|
202
|
+
const key = node.key.name;
|
|
203
|
+
const isOptional = !!node.optional; // check if property is optional
|
|
204
|
+
const typeName = node.typeAnnotation?.typeAnnotation?.typeName?.name;
|
|
205
|
+
let description = null;
|
|
206
|
+
// get comments for field
|
|
207
|
+
if (node.trailingComments && node.trailingComments.length) {
|
|
208
|
+
description = node.trailingComments[0].value.trim(); // get first comment
|
|
209
|
+
}
|
|
210
|
+
const options = {};
|
|
211
|
+
if (description) {
|
|
212
|
+
options.description = description;
|
|
213
|
+
}
|
|
214
|
+
if (this.contentType === "params") {
|
|
215
|
+
options.required = !isOptional;
|
|
216
|
+
}
|
|
217
|
+
else if (this.contentType === "body") {
|
|
218
|
+
options.nullable = isOptional;
|
|
219
|
+
}
|
|
220
|
+
return options;
|
|
221
|
+
}
|
|
222
|
+
createRequestParamsSchema(params, isPathParam = false) {
|
|
223
|
+
const queryParams = [];
|
|
224
|
+
if (params.properties) {
|
|
225
|
+
for (let [name, value] of Object.entries(params.properties)) {
|
|
226
|
+
const param = {
|
|
227
|
+
in: isPathParam ? "path" : "query",
|
|
228
|
+
name,
|
|
229
|
+
schema: {
|
|
230
|
+
type: value.type,
|
|
231
|
+
},
|
|
232
|
+
required: value.required,
|
|
233
|
+
};
|
|
234
|
+
if (value.enum) {
|
|
235
|
+
param.schema.enum = value.enum;
|
|
236
|
+
}
|
|
237
|
+
if (value.description) {
|
|
238
|
+
param.description = value.description;
|
|
239
|
+
param.schema.description = value.description;
|
|
240
|
+
}
|
|
241
|
+
queryParams.push(param);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
return queryParams;
|
|
245
|
+
}
|
|
246
|
+
createRequestBodySchema(body) {
|
|
247
|
+
return {
|
|
248
|
+
content: {
|
|
249
|
+
"application/json": {
|
|
250
|
+
schema: body,
|
|
251
|
+
},
|
|
252
|
+
},
|
|
253
|
+
};
|
|
254
|
+
}
|
|
255
|
+
createResponseSchema(responses) {
|
|
256
|
+
return {
|
|
257
|
+
200: {
|
|
258
|
+
description: "Successful response",
|
|
259
|
+
content: {
|
|
260
|
+
"application/json": {
|
|
261
|
+
schema: responses,
|
|
262
|
+
},
|
|
263
|
+
},
|
|
264
|
+
},
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
getSchemaContent({ paramsType, pathParamsType, bodyType, responseType }) {
|
|
268
|
+
let params = this.openapiDefinitions[paramsType];
|
|
269
|
+
let pathParams = this.openapiDefinitions[pathParamsType];
|
|
270
|
+
let body = this.openapiDefinitions[bodyType];
|
|
271
|
+
let responses = this.openapiDefinitions[responseType];
|
|
272
|
+
if (paramsType && !params) {
|
|
273
|
+
this.findSchemaDefinition(paramsType, "params");
|
|
274
|
+
params = this.openapiDefinitions[paramsType];
|
|
275
|
+
}
|
|
276
|
+
if (pathParamsType && !pathParams) {
|
|
277
|
+
this.findSchemaDefinition(pathParamsType, "pathParams");
|
|
278
|
+
pathParams = this.openapiDefinitions[pathParamsType];
|
|
279
|
+
}
|
|
280
|
+
if (bodyType && !body) {
|
|
281
|
+
this.findSchemaDefinition(bodyType, "body");
|
|
282
|
+
body = this.openapiDefinitions[bodyType];
|
|
283
|
+
}
|
|
284
|
+
if (responseType && !responses) {
|
|
285
|
+
this.findSchemaDefinition(responseType, "response");
|
|
286
|
+
responses = this.openapiDefinitions[responseType];
|
|
287
|
+
}
|
|
288
|
+
return {
|
|
289
|
+
params,
|
|
290
|
+
pathParams,
|
|
291
|
+
body,
|
|
292
|
+
responses,
|
|
293
|
+
};
|
|
294
|
+
}
|
|
295
|
+
}
|