@jayfong/x-server 2.32.0 → 2.35.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.
@@ -1,279 +0,0 @@
1
- import path from 'node:path';
2
- import * as parseComment from 'comment-parser';
3
- import createDebug from 'debug';
4
- import fs from 'fs-extra';
5
- import * as ts from 'ts-morph';
6
- import { ii, pascalCase, snakeCase } from 'vtils';
7
- import { HandlerMethodToHttpMethod } from "../core/http_method";
8
- export class ApiGenerator1 {
9
- constructor() {
10
- this.debug = createDebug('api1');
11
- this.cwd = process.cwd();
12
- // cwd = '/Users/admin/Documents/jfWorks/x-server-test'
13
- this.project = void 0;
14
- }
15
- getTypeBySymbol(symbol) {
16
- return symbol.getTypeAtLocation(symbol.getDeclarations()[0] || symbol.getValueDeclarationOrThrow());
17
- }
18
- getComment(declaration) {
19
- var _declaration$getLeadi;
20
- const text = ((declaration == null || (_declaration$getLeadi = declaration.getLeadingCommentRanges()[0]) == null ? void 0 : _declaration$getLeadi.getText()) || '').trim();
21
- const comment = parseComment.parse(text)[0];
22
- const description = (comment == null ? void 0 : comment.description) || '';
23
- const tags = new Map();
24
- comment == null ? void 0 : comment.tags.forEach(tag => {
25
- tags.set(tag.tag, tag.description);
26
- });
27
- return {
28
- existing: !!comment,
29
- description: description,
30
- tags: tags
31
- };
32
- }
33
- getCommentBySymbol(symbol) {
34
- var _this$getComment;
35
- return ((_this$getComment = this.getComment(symbol.getDeclarations()[0])) == null ? void 0 : _this$getComment.description) || '';
36
- }
37
- typeToApiData(type, _symbol) {
38
- var _type$getSymbol, _type$getSymbol2, _type$getSymbol3;
39
- // ws
40
- if (((_type$getSymbol = type.getSymbol()) == null ? void 0 : _type$getSymbol.getName()) === 'SocketStream') {
41
- return {
42
- name: 'ws',
43
- desc: 'ws',
44
- required: false,
45
- type: 'object',
46
- children: [],
47
- enum: []
48
- };
49
- }
50
-
51
- // XFile
52
- if (((_type$getSymbol2 = type.getSymbol()) == null ? void 0 : _type$getSymbol2.getName()) === 'MultipartFile') {
53
- const symbol = _symbol || type.getSymbol();
54
- return {
55
- name: 'file',
56
- desc: symbol && this.getCommentBySymbol(symbol) || '',
57
- required: !!symbol && !(symbol.getFlags() & ts.SymbolFlags.Optional),
58
- type: 'file',
59
- children: [],
60
- enum: []
61
- };
62
- }
63
- let isRequired = true;
64
- let isUnion = type.isUnion();
65
- const unionTypes = isUnion ? type.getUnionTypes().filter(item => !item.isBooleanLiteral() && !item.isNull() && !item.isUndefined()) : [];
66
- isUnion = !!unionTypes.length;
67
- if (isUnion) {
68
- if (unionTypes.length === 1 && !unionTypes[0].isLiteral()) {
69
- isUnion = false;
70
- }
71
- // 兼容 prisma 生成的类型用 null 表示可选
72
- isRequired = unionTypes.length === type.getUnionTypes().length;
73
- // 必须用 getBaseTypeOfLiteralType 获取枚举字面量的原始类型
74
- type = unionTypes[0].getBaseTypeOfLiteralType();
75
- }
76
- const isEnum = type.isEnum();
77
- const enumData = isEnum ?
78
- // @ts-ignore
79
- type.compilerType.types.reduce((res, item) => {
80
- res[item.getSymbol().getName()] = item.value;
81
- return res;
82
- }, {}) : {};
83
- const enumKeys = Object.keys(enumData);
84
- const enumValues = Object.values(enumData);
85
- const isIntersection = type.isIntersection();
86
- const intersectionTypes = isIntersection && type.getIntersectionTypes();
87
- const isArray = type.isArray();
88
- const isString = isEnum ? typeof enumValues[0] === 'string' : type.isString() || ['Date'].includes(((_type$getSymbol3 = type.getSymbol()) == null ? void 0 : _type$getSymbol3.getName()) || '');
89
- const isNumber = isEnum ? typeof enumValues[0] === 'number' : type.isNumber();
90
- const isBoolean = type.isBoolean() || type.isUnion() && type.getUnionTypes().some(item => item.isBooleanLiteral()) && type.getUnionTypes().every(item => item.isBooleanLiteral() || item.isNull() || item.isUndefined());
91
- const isObject = !isArray && !isString && !isNumber && !isBoolean && type.isObject() ||
92
- // 将交集类型视为对象
93
- isIntersection;
94
- const symbol = _symbol || type.getSymbol();
95
- const parentSymbol = symbol;
96
- const apiName = (symbol == null ? void 0 : symbol.getName()) || '__type';
97
- const apiDesc = [symbol && this.getCommentBySymbol(symbol), isEnum && `枚举:${enumKeys.map((key, index) => `${key}->${enumValues[index]}`).join('; ')}`].filter(Boolean).join('\n');
98
- const apiEnum = isUnion ? unionTypes.map(t => t.getLiteralValue()) : isEnum ? enumValues : [];
99
- const apiType = isArray ? 'array' : isString ? 'string' : isNumber ? 'number' : isBoolean ? 'boolean' : 'object';
100
- const apiRequired = isRequired === false ? false : !!symbol && !(symbol.getFlags() & ts.SymbolFlags.Optional);
101
- const apiChildren = isArray ? [this.typeToApiData(type.getArrayElementTypeOrThrow())] : isObject ? ii(() => {
102
- const context = type._context;
103
- const compilerFactory = context.compilerFactory;
104
- const rawChecker = type.compilerType.checker;
105
- let symbols = [];
106
- if (intersectionTypes) {
107
- // https://github.com/microsoft/TypeScript/issues/38184
108
- symbols = rawChecker.getAllPossiblePropertiesOfTypes(intersectionTypes.map(item => item.compilerType))
109
- // https://github.com/dsherret/ts-morph/blob/a7072fcf6f9babb784b40f0326c80dea4563a4aa/packages/ts-morph/src/compiler/types/Type.ts#L296
110
- .map(symbol => compilerFactory.getSymbol(symbol));
111
- } else {
112
- // symbols = type.getApparentProperties()
113
- // https://github.com/microsoft/TypeScript/issues/38184
114
- symbols = rawChecker.getAllPossiblePropertiesOfTypes([type.compilerType])
115
- // https://github.com/dsherret/ts-morph/blob/a7072fcf6f9babb784b40f0326c80dea4563a4aa/packages/ts-morph/src/compiler/types/Type.ts#L296
116
- .map(symbol => compilerFactory.getSymbol(symbol));
117
- }
118
- return symbols.map(symbol => {
119
- return this.typeToApiData(!symbol.compilerSymbol.declarations ?
120
- // 对于复杂对象,没有定义的,通过 type 直接获取(在前面通过 getText 预处理得到)
121
- symbol.compilerSymbol.type ? compilerFactory.getType(symbol.compilerSymbol.type) :
122
- // fix: symbol.compilerSymbol.type 为 undefined
123
- // https://github.com/styleguidist/react-docgen-typescript/blob/master/src/parser.ts#L696
124
- compilerFactory.getType(rawChecker.getTypeOfSymbolAtLocation(
125
- // @ts-ignore
126
- symbol.compilerSymbol,
127
- // @ts-ignore
128
- parentSymbol.compilerSymbol.declarations[0])) : this.getTypeBySymbol(symbol), symbol);
129
- });
130
- }) : [];
131
- return {
132
- name: apiName,
133
- desc: apiDesc,
134
- enum: apiEnum,
135
- type: apiType,
136
- required: apiRequired,
137
- children: apiChildren
138
- };
139
- }
140
- apiDataToJsonSchema(apiData, jsonSchema = {}) {
141
- jsonSchema.description = apiData.desc;
142
- if (apiData.type === 'object') {
143
- jsonSchema.type = 'object';
144
- jsonSchema.properties = apiData.children.reduce((res, item) => {
145
- res[item.name] = this.apiDataToJsonSchema(item);
146
- return res;
147
- }, {});
148
- jsonSchema.required = apiData.children.filter(item => item.required).map(item => item.name);
149
- } else if (apiData.type === 'array') {
150
- jsonSchema.type = 'array';
151
- jsonSchema.items = apiData.children.map(item => this.apiDataToJsonSchema(item))[0];
152
- } else {
153
- jsonSchema.type = apiData.type;
154
- if (apiData.enum.length) {
155
- jsonSchema.enum = apiData.enum;
156
- }
157
- }
158
- return jsonSchema;
159
- }
160
- genYApiData(handles) {
161
- const data = {};
162
- for (const handle of handles) {
163
- data[handle.category] = data[handle.category] || [];
164
- data[handle.category].push(handle.handlerMethod === 'GET' ? {
165
- method: handle.method.toUpperCase(),
166
- title: handle.name,
167
- path: handle.path,
168
- res_body_type: 'json',
169
- req_body_is_json_schema: false,
170
- res_body_is_json_schema: true,
171
- req_params: [],
172
- req_query: handle.requestData.children.map(item => ({
173
- required: item.required ? 1 : 0,
174
- name: item.name,
175
- desc: item.desc,
176
- type: 'string'
177
- })),
178
- req_headers: [],
179
- req_body_form: [],
180
- res_body: JSON.stringify(handle.responseDataJsonSchema)
181
- } : handle.handlerMethod === 'FILE' ? {
182
- method: handle.method.toUpperCase(),
183
- title: handle.name,
184
- path: handle.path,
185
- req_body_type: 'form',
186
- res_body_type: 'json',
187
- req_body_is_json_schema: false,
188
- res_body_is_json_schema: true,
189
- req_params: [],
190
- req_query: [],
191
- req_headers: [],
192
- req_body_form: handle.requestData.children.map(item => ({
193
- required: item.required ? 1 : 0,
194
- name: item.name,
195
- desc: item.desc,
196
- type: item.type === 'file' ? 'file' : 'text'
197
- })),
198
- res_body: JSON.stringify(handle.responseDataJsonSchema)
199
- } : {
200
- method: handle.method.toUpperCase(),
201
- title: handle.name,
202
- path: handle.path,
203
- req_body_type: 'json',
204
- res_body_type: 'json',
205
- req_body_is_json_schema: true,
206
- res_body_is_json_schema: true,
207
- req_params: [],
208
- req_query: [],
209
- req_headers: [],
210
- req_body_form: [],
211
- req_body_other: JSON.stringify(handle.requestDataJsonSchema),
212
- res_body: JSON.stringify(handle.responseDataJsonSchema)
213
- });
214
- }
215
- return Object.keys(data).map(cat => ({
216
- name: cat,
217
- desc: cat,
218
- list: data[cat]
219
- }));
220
- }
221
- async generate() {
222
- this.debug('启动项目...');
223
- this.project = new ts.Project({
224
- tsConfigFilePath: path.join(this.cwd, 'tsconfig.json')
225
- });
226
- this.debug('加载文件...');
227
- const sourceFile = this.project.createSourceFile(path.join(this.cwd, 'src/generated/handlers.ts'), await fs.readFile(path.join(this.cwd, 'node_modules/.x/handlers.ts'), 'utf-8'));
228
- this.debug('导出处理器...');
229
- const handlerGroup = sourceFile.getExportSymbols();
230
- const handles = [];
231
- this.debug('生成API文档...');
232
- for (const handlerList of handlerGroup) {
233
- const handlerListSourceFile = this.getTypeBySymbol(handlerList).getProperties()[0].getDeclarations()[0].getSourceFile();
234
- const basePath = `/${handlerListSourceFile.getFilePath().replace('.ts', '').split('/src/handlers/')[1].replace(/(^|\/)index$/, '/').split('/').map(v => snakeCase(v)).join('/')}/`.replace(/\/{2,}/g, '/');
235
- for (const handler of handlerListSourceFile.getVariableStatements().filter(item => item.isExported())) {
236
- // 重要:这一步必须,先调一遍 getText 获取看到的对象,后续对于复杂定义才不会报错
237
- handler.getDeclarations().forEach(exp => {
238
- exp.getType().getText();
239
- });
240
- const handlerExp = handler.getDeclarations()[0];
241
- const handlerPath = `${basePath}${handlerExp.getName()}`;
242
- this.debug('生成接口: %s ...', handlerPath);
243
- const [requestType, responseType, methodType] = handlerExp.getType().getTypeArguments();
244
- const handlerComment = this.getComment(handlerExp.getParent().getParent());
245
- if (handlerComment.tags.has('private')) {
246
- this.debug('跳过生成接口: %s', `${handlerPath}`);
247
- continue;
248
- }
249
- const handlerCategory = pascalCase(basePath) || 'Index';
250
- const handlerName = handlerComment.description || handlerPath;
251
- const handlerMethod = methodType.getLiteralValueOrThrow();
252
- const serverMethod = HandlerMethodToHttpMethod[handlerMethod];
253
- const requestData = this.typeToApiData(requestType);
254
- const responseData = this.typeToApiData(responseType);
255
- const requestDataJsonSchema = this.apiDataToJsonSchema(requestData);
256
- const responseDataJsonSchema = this.apiDataToJsonSchema(responseData);
257
- handles.push({
258
- category: handlerCategory,
259
- name: handlerName,
260
- path: handlerPath,
261
- handlerMethod: handlerMethod,
262
- method: serverMethod,
263
- requestData: requestData,
264
- responseData: responseData,
265
- requestDataJsonSchema: requestDataJsonSchema,
266
- responseDataJsonSchema: responseDataJsonSchema
267
- });
268
- }
269
- }
270
- this.debug('写入文件...');
271
- await Promise.all([fs.outputJSON(path.join(this.cwd, 'temp/api1.json'), handles, {
272
- spaces: 2
273
- }), fs.outputJSON(path.join(this.cwd, 'temp/yapi1.json'), this.genYApiData(handles), {
274
- spaces: 2
275
- })]);
276
- }
277
- }
278
-
279
- // new ApiGenerator().generate()