@jayfong/x-server 2.30.3 → 2.32.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/lib/_cjs/cli/api_generator.js +270 -209
- package/lib/_cjs/cli/api_generator1.js +286 -0
- package/lib/_cjs/cli/cli.js +5 -2
- package/lib/cli/api_generator.d.ts +32 -24
- package/lib/cli/api_generator.js +272 -210
- package/lib/cli/api_generator1.d.ts +44 -0
- package/lib/cli/api_generator1.js +279 -0
- package/lib/cli/cli.js +5 -2
- package/lib/core/types.d.ts +2 -0
- package/lib/services/jwt.d.ts +2 -0
- package/package.json +7 -5
|
@@ -1,137 +1,255 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
|
-
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault").default;
|
|
4
3
|
var _interopRequireWildcard = require("@babel/runtime/helpers/interopRequireWildcard").default;
|
|
4
|
+
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault").default;
|
|
5
5
|
exports.__esModule = true;
|
|
6
6
|
exports.ApiGenerator = void 0;
|
|
7
|
-
var
|
|
7
|
+
var _path = _interopRequireDefault(require("path"));
|
|
8
8
|
var _debug = _interopRequireDefault(require("debug"));
|
|
9
9
|
var _fsExtra = _interopRequireDefault(require("fs-extra"));
|
|
10
|
-
var
|
|
11
|
-
var ts = _interopRequireWildcard(require("ts-morph"));
|
|
10
|
+
var _tsMorph = _interopRequireWildcard(require("ts-morph"));
|
|
12
11
|
var _vtils = require("vtils");
|
|
13
12
|
var _http_method = require("../core/http_method");
|
|
13
|
+
/** 注释 */
|
|
14
|
+
|
|
15
|
+
/** 接口传输数据 */
|
|
16
|
+
|
|
17
|
+
/** 接口数据 */
|
|
18
|
+
|
|
14
19
|
class ApiGenerator {
|
|
15
|
-
constructor() {
|
|
20
|
+
constructor(cwd = process.cwd()) {
|
|
21
|
+
this.cwd = cwd;
|
|
16
22
|
this.debug = (0, _debug.default)('api');
|
|
17
|
-
|
|
18
|
-
//
|
|
19
|
-
|
|
23
|
+
// 几种情况:
|
|
24
|
+
// /index.ts
|
|
25
|
+
// /user/index.ts
|
|
26
|
+
// /post.ts
|
|
27
|
+
// /user/list.ts
|
|
28
|
+
this.categoryUrlRe = /^.+\/src\/handlers\/(?:index\.ts|(.*)\/index\.ts|(.*)\.ts)$/;
|
|
29
|
+
this.checker = void 0;
|
|
20
30
|
}
|
|
21
|
-
|
|
22
|
-
|
|
31
|
+
async start() {
|
|
32
|
+
this.debug('启动项目...');
|
|
33
|
+
const tsConfigPath = _path.default.join(this.cwd, 'tsconfig.json');
|
|
34
|
+
const handlersPath = _path.default.join(this.cwd, 'node_modules/.x/handlers.ts');
|
|
35
|
+
const entryFile = _path.default.join(this.cwd, 'src/generated/handlers.ts');
|
|
36
|
+
|
|
37
|
+
// 仅使用 ts-morph 初始化项目
|
|
38
|
+
const moProject = new _tsMorph.default.Project({
|
|
39
|
+
tsConfigFilePath: tsConfigPath
|
|
40
|
+
});
|
|
41
|
+
moProject.createSourceFile(entryFile, await _fsExtra.default.readFile(handlersPath, 'utf-8'));
|
|
42
|
+
const program = moProject.getProgram().compilerObject;
|
|
43
|
+
const checker = program.getTypeChecker();
|
|
44
|
+
this.checker = checker;
|
|
45
|
+
const entrySourceFile = program.getSourceFile(entryFile);
|
|
46
|
+
const moduleSymbol = checker.getSymbolAtLocation(entrySourceFile);
|
|
47
|
+
const categorySymbols = checker.getExportsOfModule(moduleSymbol);
|
|
48
|
+
const apiData = [];
|
|
49
|
+
for (const categorySymbol of categorySymbols) {
|
|
50
|
+
var _ref, _categoryUrlMatch$;
|
|
51
|
+
// 分类的类型
|
|
52
|
+
const categoryType = checker.getTypeOfSymbol(categorySymbol);
|
|
53
|
+
|
|
54
|
+
// 处理器列表
|
|
55
|
+
const handlerSymbols = categoryType.getProperties();
|
|
56
|
+
|
|
57
|
+
// 分类源文件
|
|
58
|
+
const categorySourceFile = handlerSymbols[0].getDeclarations()[0].getSourceFile();
|
|
59
|
+
|
|
60
|
+
// 分类源文件路径
|
|
61
|
+
const categorySourceFilePath = categorySourceFile.fileName;
|
|
62
|
+
|
|
63
|
+
// 分类 URL
|
|
64
|
+
const categoryUrlMatch = categorySourceFilePath.match(this.categoryUrlRe);
|
|
65
|
+
let categoryUrl = (_ref = (_categoryUrlMatch$ = categoryUrlMatch[1]) != null ? _categoryUrlMatch$ : categoryUrlMatch[2]) != null ? _ref : '';
|
|
66
|
+
categoryUrl = categoryUrl === '' ? '/' : `/${categoryUrl}/`;
|
|
67
|
+
for (const handlerSymbol of handlerSymbols) {
|
|
68
|
+
// 处理器路径
|
|
69
|
+
let handlerPath = [handlerSymbol.getName()];
|
|
70
|
+
const handlerNode = handlerSymbol.getDeclarations()[0];
|
|
71
|
+
|
|
72
|
+
// 支持自定义 requestPath
|
|
73
|
+
if (
|
|
74
|
+
// const xx = defineHandler
|
|
75
|
+
_tsMorph.ts.isVariableDeclaration(handlerNode) && handlerNode.initializer &&
|
|
76
|
+
// defineHandler()
|
|
77
|
+
_tsMorph.ts.isCallExpression(handlerNode.initializer) &&
|
|
78
|
+
// defineHandler({})
|
|
79
|
+
_tsMorph.ts.isObjectLiteralExpression(handlerNode.initializer.arguments[0])) {
|
|
80
|
+
const props = handlerNode.initializer.arguments[0].properties;
|
|
81
|
+
const requestPathNode = props.find(item => {
|
|
82
|
+
var _item$name;
|
|
83
|
+
return ((_item$name = item.name) == null ? void 0 : _item$name.getText()) === 'requestPath';
|
|
84
|
+
});
|
|
85
|
+
if (requestPathNode &&
|
|
86
|
+
// requestPath: ''
|
|
87
|
+
_tsMorph.ts.isPropertyAssignment(requestPathNode)) {
|
|
88
|
+
const customHandlerPath =
|
|
89
|
+
// requestPath: 'xx'
|
|
90
|
+
_tsMorph.ts.isStringLiteral(requestPathNode.initializer) ? requestPathNode.initializer.text :
|
|
91
|
+
// requestPath: ['xx']
|
|
92
|
+
_tsMorph.ts.isArrayLiteralExpression(requestPathNode.initializer) ? requestPathNode.initializer.elements.filter(item => _tsMorph.ts.isStringLiteral(item)).map(item => item.text) : '';
|
|
93
|
+
if (customHandlerPath.length) {
|
|
94
|
+
handlerPath = (0, _vtils.castArray)(customHandlerPath);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// 处理器 URL
|
|
100
|
+
const handlerUrl = handlerPath.map(path => `${categoryUrl}${path}`);
|
|
101
|
+
|
|
102
|
+
// 处理器注释
|
|
103
|
+
const handlerComment = this.getComment(handlerSymbol);
|
|
104
|
+
if (handlerComment.tags.has('private')) {
|
|
105
|
+
this.debug('跳过生成接口: %s', `${handlerUrl}`);
|
|
106
|
+
continue;
|
|
107
|
+
}
|
|
108
|
+
this.debug('生成接口: %s ...', handlerUrl);
|
|
109
|
+
const handlerType = checker.getTypeAtLocation(handlerNode);
|
|
110
|
+
const [requestType, responseType, methodType] = checker.getTypeArguments(handlerType);
|
|
111
|
+
const handlerCategory = (0, _vtils.pascalCase)(categoryUrl) || 'Index';
|
|
112
|
+
const handlerDescription = handlerComment.description || handlerUrl.join(', ');
|
|
113
|
+
const handlerMethod = methodType.value;
|
|
114
|
+
const httpMethod = _http_method.HandlerMethodToHttpMethod[handlerMethod];
|
|
115
|
+
const requestData = this.getApiDto({
|
|
116
|
+
type: requestType
|
|
117
|
+
});
|
|
118
|
+
const responseData = this.getApiDto({
|
|
119
|
+
type: responseType
|
|
120
|
+
});
|
|
121
|
+
const requestDataJsonSchema = this.apiDataToJsonSchema(requestData);
|
|
122
|
+
const responseDataJsonSchema = this.apiDataToJsonSchema(responseData);
|
|
123
|
+
apiData.push({
|
|
124
|
+
category: handlerCategory,
|
|
125
|
+
name: handlerDescription,
|
|
126
|
+
path: handlerUrl,
|
|
127
|
+
handlerMethod: handlerMethod,
|
|
128
|
+
method: httpMethod,
|
|
129
|
+
requestData: requestData,
|
|
130
|
+
responseData: responseData,
|
|
131
|
+
requestDataJsonSchema: requestDataJsonSchema,
|
|
132
|
+
responseDataJsonSchema: responseDataJsonSchema
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
this.debug('写入文件...');
|
|
137
|
+
await Promise.all([_fsExtra.default.outputJSON(_path.default.join(this.cwd, 'temp/api.json'), apiData, {
|
|
138
|
+
spaces: 2
|
|
139
|
+
}), _fsExtra.default.outputJSON(_path.default.join(this.cwd, 'temp/yapi.json'), this.genYApiData(apiData), {
|
|
140
|
+
spaces: 2
|
|
141
|
+
})]);
|
|
23
142
|
}
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
143
|
+
|
|
144
|
+
// ref: https://github.com/styleguidist/react-docgen-typescript/blob/master/src/parser.ts#L777
|
|
145
|
+
getComment(symbol) {
|
|
146
|
+
if (!symbol) {
|
|
147
|
+
return {
|
|
148
|
+
existing: false,
|
|
149
|
+
description: '',
|
|
150
|
+
tags: new Map()
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
const description = _tsMorph.ts.displayPartsToString(symbol.getDocumentationComment(this.checker)).trim();
|
|
154
|
+
const tags = symbol.getJsDocTags().map(item => {
|
|
155
|
+
return {
|
|
156
|
+
label: item.name,
|
|
157
|
+
value: _tsMorph.ts.displayPartsToString(item.text).trim()
|
|
158
|
+
};
|
|
32
159
|
});
|
|
33
160
|
return {
|
|
34
|
-
existing: !!
|
|
161
|
+
existing: !!description,
|
|
35
162
|
description: description,
|
|
36
|
-
tags: tags
|
|
163
|
+
tags: tags.reduce((res, item) => {
|
|
164
|
+
res.set(item.label, item.value);
|
|
165
|
+
return res;
|
|
166
|
+
}, new Map())
|
|
37
167
|
};
|
|
38
168
|
}
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
169
|
+
getApiDto({
|
|
170
|
+
type,
|
|
171
|
+
keySymbol = type.symbol || type.aliasSymbol
|
|
172
|
+
}) {
|
|
173
|
+
var _valueSymbol;
|
|
174
|
+
const hasTypeFlag = (flag, _type = type) => (_type.flags & flag) === flag;
|
|
175
|
+
let valueSymbol = type.symbol || type.aliasSymbol;
|
|
176
|
+
const keySymbolName = (keySymbol == null ? void 0 : keySymbol.getName()) || '';
|
|
177
|
+
let valueSymbolName = ((_valueSymbol = valueSymbol) == null ? void 0 : _valueSymbol.getName()) || '';
|
|
178
|
+
const comment = this.getComment(keySymbol);
|
|
179
|
+
let isRequired = keySymbol ? !(keySymbol.getFlags() & _tsMorph.ts.SymbolFlags.Optional) : false;
|
|
180
|
+
|
|
181
|
+
// WS
|
|
182
|
+
if (valueSymbolName === 'SocketStream') {
|
|
47
183
|
return {
|
|
48
184
|
name: 'ws',
|
|
49
|
-
desc:
|
|
50
|
-
required:
|
|
185
|
+
desc: comment.description,
|
|
186
|
+
required: isRequired,
|
|
51
187
|
type: 'object',
|
|
52
|
-
|
|
53
|
-
|
|
188
|
+
enum: [],
|
|
189
|
+
children: []
|
|
54
190
|
};
|
|
55
191
|
}
|
|
56
192
|
|
|
57
|
-
//
|
|
58
|
-
if (
|
|
59
|
-
const symbol = _symbol || type.getSymbol();
|
|
193
|
+
// 文件
|
|
194
|
+
if (valueSymbolName === 'MultipartFile') {
|
|
60
195
|
return {
|
|
61
196
|
name: 'file',
|
|
62
|
-
desc:
|
|
63
|
-
required:
|
|
197
|
+
desc: comment.description,
|
|
198
|
+
required: isRequired,
|
|
64
199
|
type: 'file',
|
|
65
|
-
|
|
66
|
-
|
|
200
|
+
enum: [],
|
|
201
|
+
children: []
|
|
67
202
|
};
|
|
68
203
|
}
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
204
|
+
|
|
205
|
+
// 联合类型
|
|
206
|
+
// 布尔、枚举也会用这表示
|
|
207
|
+
const rawUnionTypes = type.isUnion() && type.types || [];
|
|
208
|
+
const unionTypes = rawUnionTypes.filter(item => !hasTypeFlag(_tsMorph.ts.TypeFlags.Null, item) && !hasTypeFlag(_tsMorph.ts.TypeFlags.Undefined, item));
|
|
209
|
+
const isUnion = !!unionTypes.length;
|
|
73
210
|
if (isUnion) {
|
|
74
|
-
|
|
75
|
-
|
|
211
|
+
var _this$checker$getBase;
|
|
212
|
+
if (unionTypes.length !== rawUnionTypes.length) {
|
|
213
|
+
isRequired = false;
|
|
76
214
|
}
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
type = unionTypes[0].getBaseTypeOfLiteralType();
|
|
215
|
+
type = unionTypes[0];
|
|
216
|
+
valueSymbol = type.symbol || type.aliasSymbol;
|
|
217
|
+
valueSymbolName = ((_this$checker$getBase = this.checker.getBaseTypeOfLiteralType(type).getSymbol()) == null ? void 0 : _this$checker$getBase.getName()) || '';
|
|
81
218
|
}
|
|
82
|
-
const isEnum = type.isEnum();
|
|
83
|
-
const enumData = isEnum ?
|
|
84
|
-
// @ts-ignore
|
|
85
|
-
type.compilerType.types.reduce((res, item) => {
|
|
86
|
-
res[item.getSymbol().getName()] = item.value;
|
|
87
|
-
return res;
|
|
88
|
-
}, {}) : {};
|
|
89
|
-
const enumKeys = Object.keys(enumData);
|
|
90
|
-
const enumValues = Object.values(enumData);
|
|
91
219
|
const isIntersection = type.isIntersection();
|
|
92
|
-
const intersectionTypes = isIntersection && type.
|
|
93
|
-
const
|
|
94
|
-
const
|
|
95
|
-
const
|
|
96
|
-
const
|
|
97
|
-
const
|
|
98
|
-
|
|
220
|
+
const intersectionTypes = isIntersection && type.types || [];
|
|
221
|
+
const isDate = valueSymbolName === 'Date';
|
|
222
|
+
const isBigIntLiteral = hasTypeFlag(_tsMorph.ts.TypeFlags.BigIntLiteral);
|
|
223
|
+
const isBigInt = hasTypeFlag(_tsMorph.ts.TypeFlags.BigInt) || isBigIntLiteral;
|
|
224
|
+
const isArray = valueSymbolName === 'Array' || valueSymbolName === 'ReadonlyArray';
|
|
225
|
+
const isStringLiteral = hasTypeFlag(_tsMorph.ts.TypeFlags.StringLiteral);
|
|
226
|
+
const isString = hasTypeFlag(_tsMorph.ts.TypeFlags.String) || isStringLiteral ||
|
|
227
|
+
// BigInt 类型视为字符串
|
|
228
|
+
isBigInt ||
|
|
229
|
+
// Date 类型视为字符串
|
|
230
|
+
isDate;
|
|
231
|
+
const isNumberLiteral = hasTypeFlag(_tsMorph.ts.TypeFlags.NumberLiteral);
|
|
232
|
+
const isNumber = hasTypeFlag(_tsMorph.ts.TypeFlags.Number) || isNumberLiteral;
|
|
233
|
+
const isBoolean = hasTypeFlag(_tsMorph.ts.TypeFlags.Boolean) || hasTypeFlag(_tsMorph.ts.TypeFlags.BooleanLiteral);
|
|
234
|
+
const isLiteral = isStringLiteral || isNumberLiteral || isBigIntLiteral;
|
|
235
|
+
const isObject = !isArray && !isString && !isNumber && !isBoolean && hasTypeFlag(_tsMorph.ts.TypeFlags.Object) ||
|
|
236
|
+
// 交集类型视为对象
|
|
99
237
|
isIntersection;
|
|
100
|
-
const
|
|
101
|
-
const
|
|
102
|
-
const
|
|
103
|
-
const apiDesc = [symbol && this.getCommentBySymbol(symbol), isEnum && `枚举:${enumKeys.map((key, index) => `${key}->${enumValues[index]}`).join('; ')}`].filter(Boolean).join('\n');
|
|
104
|
-
const apiEnum = isUnion ? unionTypes.map(t => t.getLiteralValue()) : isEnum ? enumValues : [];
|
|
238
|
+
const apiName = keySymbolName || '__type';
|
|
239
|
+
const apiDesc = keySymbol && this.getComment(keySymbol).description || '';
|
|
240
|
+
const apiEnum = (isUnion ? unionTypes.map(item => item.value) : isLiteral ? [type.value] : []).filter(v => v != null);
|
|
105
241
|
const apiType = isArray ? 'array' : isString ? 'string' : isNumber ? 'number' : isBoolean ? 'boolean' : 'object';
|
|
106
|
-
const apiRequired = isRequired
|
|
107
|
-
const apiChildren = isArray ? [this.
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
const
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
} else {
|
|
118
|
-
// symbols = type.getApparentProperties()
|
|
119
|
-
// https://github.com/microsoft/TypeScript/issues/38184
|
|
120
|
-
symbols = rawChecker.getAllPossiblePropertiesOfTypes([type.compilerType])
|
|
121
|
-
// https://github.com/dsherret/ts-morph/blob/a7072fcf6f9babb784b40f0326c80dea4563a4aa/packages/ts-morph/src/compiler/types/Type.ts#L296
|
|
122
|
-
.map(symbol => compilerFactory.getSymbol(symbol));
|
|
123
|
-
}
|
|
124
|
-
return symbols.map(symbol => {
|
|
125
|
-
return this.typeToApiData(!symbol.compilerSymbol.declarations ?
|
|
126
|
-
// 对于复杂对象,没有定义的,通过 type 直接获取(在前面通过 getText 预处理得到)
|
|
127
|
-
symbol.compilerSymbol.type ? compilerFactory.getType(symbol.compilerSymbol.type) :
|
|
128
|
-
// fix: symbol.compilerSymbol.type 为 undefined
|
|
129
|
-
// https://github.com/styleguidist/react-docgen-typescript/blob/master/src/parser.ts#L696
|
|
130
|
-
compilerFactory.getType(rawChecker.getTypeOfSymbolAtLocation(
|
|
131
|
-
// @ts-ignore
|
|
132
|
-
symbol.compilerSymbol,
|
|
133
|
-
// @ts-ignore
|
|
134
|
-
parentSymbol.compilerSymbol.declarations[0])) : this.getTypeBySymbol(symbol), symbol);
|
|
242
|
+
const apiRequired = isRequired;
|
|
243
|
+
const apiChildren = isArray ? [this.getApiDto({
|
|
244
|
+
type: this.checker.getTypeArguments(type)[0]
|
|
245
|
+
})] : isObject ? (0, _vtils.ii)(() => {
|
|
246
|
+
const childSymbols = this.checker.getAllPossiblePropertiesOfTypes(intersectionTypes.length ? intersectionTypes : [type]);
|
|
247
|
+
return childSymbols.map(childSymbol => {
|
|
248
|
+
const childType = this.checker.getTypeOfSymbol(childSymbol);
|
|
249
|
+
return this.getApiDto({
|
|
250
|
+
type: childType,
|
|
251
|
+
keySymbol: childSymbol
|
|
252
|
+
});
|
|
135
253
|
});
|
|
136
254
|
}) : [];
|
|
137
255
|
return {
|
|
@@ -148,7 +266,6 @@ class ApiGenerator {
|
|
|
148
266
|
if (apiData.type === 'object') {
|
|
149
267
|
jsonSchema.type = 'object';
|
|
150
268
|
jsonSchema.properties = apiData.children.reduce((res, item) => {
|
|
151
|
-
;
|
|
152
269
|
res[item.name] = this.apiDataToJsonSchema(item);
|
|
153
270
|
return res;
|
|
154
271
|
}, {});
|
|
@@ -167,57 +284,59 @@ class ApiGenerator {
|
|
|
167
284
|
genYApiData(handles) {
|
|
168
285
|
const data = {};
|
|
169
286
|
for (const handle of handles) {
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
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
|
-
|
|
287
|
+
for (const path of handle.path) {
|
|
288
|
+
data[handle.category] = data[handle.category] || [];
|
|
289
|
+
data[handle.category].push(handle.handlerMethod === 'GET' ? {
|
|
290
|
+
method: handle.method.toUpperCase(),
|
|
291
|
+
title: handle.name,
|
|
292
|
+
path: path,
|
|
293
|
+
res_body_type: 'json',
|
|
294
|
+
req_body_is_json_schema: false,
|
|
295
|
+
res_body_is_json_schema: true,
|
|
296
|
+
req_params: [],
|
|
297
|
+
req_query: handle.requestData.children.map(item => ({
|
|
298
|
+
required: item.required ? 1 : 0,
|
|
299
|
+
name: item.name,
|
|
300
|
+
desc: item.desc,
|
|
301
|
+
type: 'string'
|
|
302
|
+
})),
|
|
303
|
+
req_headers: [],
|
|
304
|
+
req_body_form: [],
|
|
305
|
+
res_body: JSON.stringify(handle.responseDataJsonSchema)
|
|
306
|
+
} : handle.handlerMethod === 'FILE' ? {
|
|
307
|
+
method: handle.method.toUpperCase(),
|
|
308
|
+
title: handle.name,
|
|
309
|
+
path: path,
|
|
310
|
+
req_body_type: 'form',
|
|
311
|
+
res_body_type: 'json',
|
|
312
|
+
req_body_is_json_schema: false,
|
|
313
|
+
res_body_is_json_schema: true,
|
|
314
|
+
req_params: [],
|
|
315
|
+
req_query: [],
|
|
316
|
+
req_headers: [],
|
|
317
|
+
req_body_form: handle.requestData.children.map(item => ({
|
|
318
|
+
required: item.required ? 1 : 0,
|
|
319
|
+
name: item.name,
|
|
320
|
+
desc: item.desc,
|
|
321
|
+
type: item.type === 'file' ? 'file' : 'text'
|
|
322
|
+
})),
|
|
323
|
+
res_body: JSON.stringify(handle.responseDataJsonSchema)
|
|
324
|
+
} : {
|
|
325
|
+
method: handle.method.toUpperCase(),
|
|
326
|
+
title: handle.name,
|
|
327
|
+
path: path,
|
|
328
|
+
req_body_type: 'json',
|
|
329
|
+
res_body_type: 'json',
|
|
330
|
+
req_body_is_json_schema: true,
|
|
331
|
+
res_body_is_json_schema: true,
|
|
332
|
+
req_params: [],
|
|
333
|
+
req_query: [],
|
|
334
|
+
req_headers: [],
|
|
335
|
+
req_body_form: [],
|
|
336
|
+
req_body_other: JSON.stringify(handle.requestDataJsonSchema),
|
|
337
|
+
res_body: JSON.stringify(handle.responseDataJsonSchema)
|
|
338
|
+
});
|
|
339
|
+
}
|
|
221
340
|
}
|
|
222
341
|
return Object.keys(data).map(cat => ({
|
|
223
342
|
name: cat,
|
|
@@ -225,63 +344,5 @@ class ApiGenerator {
|
|
|
225
344
|
list: data[cat]
|
|
226
345
|
}));
|
|
227
346
|
}
|
|
228
|
-
async generate() {
|
|
229
|
-
this.debug('启动项目...');
|
|
230
|
-
this.project = new ts.Project({
|
|
231
|
-
tsConfigFilePath: _nodePath.default.join(this.cwd, 'tsconfig.json')
|
|
232
|
-
});
|
|
233
|
-
this.debug('加载文件...');
|
|
234
|
-
const sourceFile = this.project.createSourceFile(_nodePath.default.join(this.cwd, 'src/generated/handlers.ts'), await _fsExtra.default.readFile(_nodePath.default.join(this.cwd, 'node_modules/.x/handlers.ts'), 'utf-8'));
|
|
235
|
-
this.debug('导出处理器...');
|
|
236
|
-
const handlerGroup = sourceFile.getExportSymbols();
|
|
237
|
-
const handles = [];
|
|
238
|
-
this.debug('生成API文档...');
|
|
239
|
-
for (const handlerList of handlerGroup) {
|
|
240
|
-
const handlerListSourceFile = this.getTypeBySymbol(handlerList).getProperties()[0].getDeclarations()[0].getSourceFile();
|
|
241
|
-
const basePath = `/${handlerListSourceFile.getFilePath().replace('.ts', '').split('/src/handlers/')[1].replace(/(^|\/)index$/, '/').split('/').map(v => (0, _vtils.snakeCase)(v)).join('/')}/`.replace(/\/{2,}/g, '/');
|
|
242
|
-
for (const handler of handlerListSourceFile.getVariableStatements().filter(item => item.isExported())) {
|
|
243
|
-
// 重要:这一步必须,先调一遍 getText 获取看到的对象,后续对于复杂定义才不会报错
|
|
244
|
-
handler.getDeclarations().forEach(exp => {
|
|
245
|
-
exp.getType().getText();
|
|
246
|
-
});
|
|
247
|
-
const handlerExp = handler.getDeclarations()[0];
|
|
248
|
-
const handlerPath = `${basePath}${handlerExp.getName()}`;
|
|
249
|
-
this.debug('生成接口: %s ...', handlerPath);
|
|
250
|
-
const [requestType, responseType, methodType] = handlerExp.getType().getTypeArguments();
|
|
251
|
-
const handlerComment = this.getComment(handlerExp.getParent().getParent());
|
|
252
|
-
if (handlerComment.tags.has('private')) {
|
|
253
|
-
this.debug('跳过生成接口: %s', `${handlerPath}`);
|
|
254
|
-
continue;
|
|
255
|
-
}
|
|
256
|
-
const handlerCategory = (0, _vtils.pascalCase)(basePath) || 'Index';
|
|
257
|
-
const handlerName = handlerComment.description || handlerPath;
|
|
258
|
-
const handlerMethod = methodType.getLiteralValueOrThrow();
|
|
259
|
-
const serverMethod = _http_method.HandlerMethodToHttpMethod[handlerMethod];
|
|
260
|
-
const requestData = this.typeToApiData(requestType);
|
|
261
|
-
const responseData = this.typeToApiData(responseType);
|
|
262
|
-
const requestDataJsonSchema = this.apiDataToJsonSchema(requestData);
|
|
263
|
-
const responseDataJsonSchema = this.apiDataToJsonSchema(responseData);
|
|
264
|
-
handles.push({
|
|
265
|
-
category: handlerCategory,
|
|
266
|
-
name: handlerName,
|
|
267
|
-
path: handlerPath,
|
|
268
|
-
handlerMethod: handlerMethod,
|
|
269
|
-
method: serverMethod,
|
|
270
|
-
requestData: requestData,
|
|
271
|
-
responseData: responseData,
|
|
272
|
-
requestDataJsonSchema: requestDataJsonSchema,
|
|
273
|
-
responseDataJsonSchema: responseDataJsonSchema
|
|
274
|
-
});
|
|
275
|
-
}
|
|
276
|
-
}
|
|
277
|
-
this.debug('写入文件...');
|
|
278
|
-
await Promise.all([_fsExtra.default.outputJSON(_nodePath.default.join(this.cwd, 'temp/api.json'), handles, {
|
|
279
|
-
spaces: 2
|
|
280
|
-
}), _fsExtra.default.outputJSON(_nodePath.default.join(this.cwd, 'temp/yapi.json'), this.genYApiData(handles), {
|
|
281
|
-
spaces: 2
|
|
282
|
-
})]);
|
|
283
|
-
}
|
|
284
347
|
}
|
|
285
|
-
|
|
286
|
-
// new ApiGenerator().generate()
|
|
287
348
|
exports.ApiGenerator = ApiGenerator;
|