@jayfong/x-server 2.30.3 → 2.31.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.
@@ -0,0 +1,286 @@
1
+ "use strict";
2
+
3
+ var _interopRequireWildcard = require("@babel/runtime/helpers/interopRequireWildcard").default;
4
+ var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault").default;
5
+ exports.__esModule = true;
6
+ exports.ApiGenerator1 = void 0;
7
+ var _nodePath = _interopRequireDefault(require("node:path"));
8
+ var parseComment = _interopRequireWildcard(require("comment-parser"));
9
+ var _debug = _interopRequireDefault(require("debug"));
10
+ var _fsExtra = _interopRequireDefault(require("fs-extra"));
11
+ var ts = _interopRequireWildcard(require("ts-morph"));
12
+ var _vtils = require("vtils");
13
+ var _http_method = require("../core/http_method");
14
+ class ApiGenerator1 {
15
+ constructor() {
16
+ this.debug = (0, _debug.default)('api1');
17
+ this.cwd = process.cwd();
18
+ // cwd = '/Users/admin/Documents/jfWorks/x-server-test'
19
+ this.project = void 0;
20
+ }
21
+ getTypeBySymbol(symbol) {
22
+ return symbol.getTypeAtLocation(symbol.getDeclarations()[0] || symbol.getValueDeclarationOrThrow());
23
+ }
24
+ getComment(declaration) {
25
+ var _declaration$getLeadi;
26
+ const text = ((declaration == null || (_declaration$getLeadi = declaration.getLeadingCommentRanges()[0]) == null ? void 0 : _declaration$getLeadi.getText()) || '').trim();
27
+ const comment = parseComment.parse(text)[0];
28
+ const description = (comment == null ? void 0 : comment.description) || '';
29
+ const tags = new Map();
30
+ comment == null ? void 0 : comment.tags.forEach(tag => {
31
+ tags.set(tag.tag, tag.description);
32
+ });
33
+ return {
34
+ existing: !!comment,
35
+ description: description,
36
+ tags: tags
37
+ };
38
+ }
39
+ getCommentBySymbol(symbol) {
40
+ var _this$getComment;
41
+ return ((_this$getComment = this.getComment(symbol.getDeclarations()[0])) == null ? void 0 : _this$getComment.description) || '';
42
+ }
43
+ typeToApiData(type, _symbol) {
44
+ var _type$getSymbol, _type$getSymbol2, _type$getSymbol3;
45
+ // ws
46
+ if (((_type$getSymbol = type.getSymbol()) == null ? void 0 : _type$getSymbol.getName()) === 'SocketStream') {
47
+ return {
48
+ name: 'ws',
49
+ desc: 'ws',
50
+ required: false,
51
+ type: 'object',
52
+ children: [],
53
+ enum: []
54
+ };
55
+ }
56
+
57
+ // XFile
58
+ if (((_type$getSymbol2 = type.getSymbol()) == null ? void 0 : _type$getSymbol2.getName()) === 'MultipartFile') {
59
+ const symbol = _symbol || type.getSymbol();
60
+ return {
61
+ name: 'file',
62
+ desc: symbol && this.getCommentBySymbol(symbol) || '',
63
+ required: !!symbol && !(symbol.getFlags() & ts.SymbolFlags.Optional),
64
+ type: 'file',
65
+ children: [],
66
+ enum: []
67
+ };
68
+ }
69
+ let isRequired = true;
70
+ let isUnion = type.isUnion();
71
+ const unionTypes = isUnion ? type.getUnionTypes().filter(item => !item.isBooleanLiteral() && !item.isNull() && !item.isUndefined()) : [];
72
+ isUnion = !!unionTypes.length;
73
+ if (isUnion) {
74
+ if (unionTypes.length === 1 && !unionTypes[0].isLiteral()) {
75
+ isUnion = false;
76
+ }
77
+ // 兼容 prisma 生成的类型用 null 表示可选
78
+ isRequired = unionTypes.length === type.getUnionTypes().length;
79
+ // 必须用 getBaseTypeOfLiteralType 获取枚举字面量的原始类型
80
+ type = unionTypes[0].getBaseTypeOfLiteralType();
81
+ }
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
+ const isIntersection = type.isIntersection();
92
+ const intersectionTypes = isIntersection && type.getIntersectionTypes();
93
+ const isArray = type.isArray();
94
+ const isString = isEnum ? typeof enumValues[0] === 'string' : type.isString() || ['Date'].includes(((_type$getSymbol3 = type.getSymbol()) == null ? void 0 : _type$getSymbol3.getName()) || '');
95
+ const isNumber = isEnum ? typeof enumValues[0] === 'number' : type.isNumber();
96
+ const isBoolean = type.isBoolean() || type.isUnion() && type.getUnionTypes().some(item => item.isBooleanLiteral()) && type.getUnionTypes().every(item => item.isBooleanLiteral() || item.isNull() || item.isUndefined());
97
+ const isObject = !isArray && !isString && !isNumber && !isBoolean && type.isObject() ||
98
+ // 将交集类型视为对象
99
+ isIntersection;
100
+ const symbol = _symbol || type.getSymbol();
101
+ const parentSymbol = symbol;
102
+ const apiName = (symbol == null ? void 0 : symbol.getName()) || '__type';
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 : [];
105
+ const apiType = isArray ? 'array' : isString ? 'string' : isNumber ? 'number' : isBoolean ? 'boolean' : 'object';
106
+ const apiRequired = isRequired === false ? false : !!symbol && !(symbol.getFlags() & ts.SymbolFlags.Optional);
107
+ const apiChildren = isArray ? [this.typeToApiData(type.getArrayElementTypeOrThrow())] : isObject ? (0, _vtils.ii)(() => {
108
+ const context = type._context;
109
+ const compilerFactory = context.compilerFactory;
110
+ const rawChecker = type.compilerType.checker;
111
+ let symbols = [];
112
+ if (intersectionTypes) {
113
+ // https://github.com/microsoft/TypeScript/issues/38184
114
+ symbols = rawChecker.getAllPossiblePropertiesOfTypes(intersectionTypes.map(item => item.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
+ } 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);
135
+ });
136
+ }) : [];
137
+ return {
138
+ name: apiName,
139
+ desc: apiDesc,
140
+ enum: apiEnum,
141
+ type: apiType,
142
+ required: apiRequired,
143
+ children: apiChildren
144
+ };
145
+ }
146
+ apiDataToJsonSchema(apiData, jsonSchema = {}) {
147
+ jsonSchema.description = apiData.desc;
148
+ if (apiData.type === 'object') {
149
+ jsonSchema.type = 'object';
150
+ jsonSchema.properties = apiData.children.reduce((res, item) => {
151
+ res[item.name] = this.apiDataToJsonSchema(item);
152
+ return res;
153
+ }, {});
154
+ jsonSchema.required = apiData.children.filter(item => item.required).map(item => item.name);
155
+ } else if (apiData.type === 'array') {
156
+ jsonSchema.type = 'array';
157
+ jsonSchema.items = apiData.children.map(item => this.apiDataToJsonSchema(item))[0];
158
+ } else {
159
+ jsonSchema.type = apiData.type;
160
+ if (apiData.enum.length) {
161
+ jsonSchema.enum = apiData.enum;
162
+ }
163
+ }
164
+ return jsonSchema;
165
+ }
166
+ genYApiData(handles) {
167
+ const data = {};
168
+ for (const handle of handles) {
169
+ data[handle.category] = data[handle.category] || [];
170
+ data[handle.category].push(handle.handlerMethod === 'GET' ? {
171
+ method: handle.method.toUpperCase(),
172
+ title: handle.name,
173
+ path: handle.path,
174
+ res_body_type: 'json',
175
+ req_body_is_json_schema: false,
176
+ res_body_is_json_schema: true,
177
+ req_params: [],
178
+ req_query: handle.requestData.children.map(item => ({
179
+ required: item.required ? 1 : 0,
180
+ name: item.name,
181
+ desc: item.desc,
182
+ type: 'string'
183
+ })),
184
+ req_headers: [],
185
+ req_body_form: [],
186
+ res_body: JSON.stringify(handle.responseDataJsonSchema)
187
+ } : handle.handlerMethod === 'FILE' ? {
188
+ method: handle.method.toUpperCase(),
189
+ title: handle.name,
190
+ path: handle.path,
191
+ req_body_type: 'form',
192
+ res_body_type: 'json',
193
+ req_body_is_json_schema: false,
194
+ res_body_is_json_schema: true,
195
+ req_params: [],
196
+ req_query: [],
197
+ req_headers: [],
198
+ req_body_form: handle.requestData.children.map(item => ({
199
+ required: item.required ? 1 : 0,
200
+ name: item.name,
201
+ desc: item.desc,
202
+ type: item.type === 'file' ? 'file' : 'text'
203
+ })),
204
+ res_body: JSON.stringify(handle.responseDataJsonSchema)
205
+ } : {
206
+ method: handle.method.toUpperCase(),
207
+ title: handle.name,
208
+ path: handle.path,
209
+ req_body_type: 'json',
210
+ res_body_type: 'json',
211
+ req_body_is_json_schema: true,
212
+ res_body_is_json_schema: true,
213
+ req_params: [],
214
+ req_query: [],
215
+ req_headers: [],
216
+ req_body_form: [],
217
+ req_body_other: JSON.stringify(handle.requestDataJsonSchema),
218
+ res_body: JSON.stringify(handle.responseDataJsonSchema)
219
+ });
220
+ }
221
+ return Object.keys(data).map(cat => ({
222
+ name: cat,
223
+ desc: cat,
224
+ list: data[cat]
225
+ }));
226
+ }
227
+ async generate() {
228
+ this.debug('启动项目...');
229
+ this.project = new ts.Project({
230
+ tsConfigFilePath: _nodePath.default.join(this.cwd, 'tsconfig.json')
231
+ });
232
+ this.debug('加载文件...');
233
+ 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'));
234
+ this.debug('导出处理器...');
235
+ const handlerGroup = sourceFile.getExportSymbols();
236
+ const handles = [];
237
+ this.debug('生成API文档...');
238
+ for (const handlerList of handlerGroup) {
239
+ const handlerListSourceFile = this.getTypeBySymbol(handlerList).getProperties()[0].getDeclarations()[0].getSourceFile();
240
+ const basePath = `/${handlerListSourceFile.getFilePath().replace('.ts', '').split('/src/handlers/')[1].replace(/(^|\/)index$/, '/').split('/').map(v => (0, _vtils.snakeCase)(v)).join('/')}/`.replace(/\/{2,}/g, '/');
241
+ for (const handler of handlerListSourceFile.getVariableStatements().filter(item => item.isExported())) {
242
+ // 重要:这一步必须,先调一遍 getText 获取看到的对象,后续对于复杂定义才不会报错
243
+ handler.getDeclarations().forEach(exp => {
244
+ exp.getType().getText();
245
+ });
246
+ const handlerExp = handler.getDeclarations()[0];
247
+ const handlerPath = `${basePath}${handlerExp.getName()}`;
248
+ this.debug('生成接口: %s ...', handlerPath);
249
+ const [requestType, responseType, methodType] = handlerExp.getType().getTypeArguments();
250
+ const handlerComment = this.getComment(handlerExp.getParent().getParent());
251
+ if (handlerComment.tags.has('private')) {
252
+ this.debug('跳过生成接口: %s', `${handlerPath}`);
253
+ continue;
254
+ }
255
+ const handlerCategory = (0, _vtils.pascalCase)(basePath) || 'Index';
256
+ const handlerName = handlerComment.description || handlerPath;
257
+ const handlerMethod = methodType.getLiteralValueOrThrow();
258
+ const serverMethod = _http_method.HandlerMethodToHttpMethod[handlerMethod];
259
+ const requestData = this.typeToApiData(requestType);
260
+ const responseData = this.typeToApiData(responseType);
261
+ const requestDataJsonSchema = this.apiDataToJsonSchema(requestData);
262
+ const responseDataJsonSchema = this.apiDataToJsonSchema(responseData);
263
+ handles.push({
264
+ category: handlerCategory,
265
+ name: handlerName,
266
+ path: handlerPath,
267
+ handlerMethod: handlerMethod,
268
+ method: serverMethod,
269
+ requestData: requestData,
270
+ responseData: responseData,
271
+ requestDataJsonSchema: requestDataJsonSchema,
272
+ responseDataJsonSchema: responseDataJsonSchema
273
+ });
274
+ }
275
+ }
276
+ this.debug('写入文件...');
277
+ await Promise.all([_fsExtra.default.outputJSON(_nodePath.default.join(this.cwd, 'temp/api1.json'), handles, {
278
+ spaces: 2
279
+ }), _fsExtra.default.outputJSON(_nodePath.default.join(this.cwd, 'temp/yapi1.json'), this.genYApiData(handles), {
280
+ spaces: 2
281
+ })]);
282
+ }
283
+ }
284
+
285
+ // new ApiGenerator().generate()
286
+ exports.ApiGenerator1 = ApiGenerator1;
@@ -8,6 +8,7 @@ var _vscodeGenerateIndexStandalone = require("vscode-generate-index-standalone")
8
8
  var _vtils = require("vtils");
9
9
  var _yargs = _interopRequireDefault(require("yargs"));
10
10
  var _api_generator = require("./api_generator");
11
+ var _api_generator2 = require("./api_generator1");
11
12
  var _build_util = require("./build_util");
12
13
  var _deploy_util = require("./deploy_util");
13
14
  var _dev_util = require("./dev_util");
@@ -98,7 +99,7 @@ _yargs.default.command('dev', '开始开发', _ => _.positional('index', {
98
99
  default: 'production'
99
100
  }), async argv => {
100
101
  process.env.NODE_ENV = 'production';
101
- const externals = (0, _vtils.castArray)(argv.external || []).map(item => item.split(',')).flat().filter(Boolean);
102
+ const externals = (0, _vtils.castArray)(argv.external || []).flatMap(item => item.split(',')).filter(Boolean);
102
103
  const envMap = await _env_util.EnvUtil.parseFileAsMap({
103
104
  cwd: process.cwd(),
104
105
  file: ['.env', `.env.${argv.env}`]
@@ -122,7 +123,9 @@ _yargs.default.command('dev', '开始开发', _ => _.positional('index', {
122
123
  });
123
124
  console.log('构建成功');
124
125
  }).command('api', '生成 API', async () => {
125
- await new _api_generator.ApiGenerator().generate();
126
+ await new _api_generator.ApiGenerator().start();
127
+ }).command('api1', '(old) 生成 API', async () => {
128
+ await new _api_generator2.ApiGenerator1().generate();
126
129
  }).command('prisma', 'prisma 代理,主要为了注入环境变量', _ => _.positional('production', {
127
130
  alias: 'p',
128
131
  describe: '是否生产模式',
@@ -1,44 +1,52 @@
1
1
  import createDebug from 'debug';
2
- import type { JSONSchema4 } from 'json-schema';
3
- import * as ts from 'ts-morph';
4
- import type { XHandler } from '../core/types';
5
- interface ApiData {
2
+ import { type JSONSchema4 } from 'json-schema';
3
+ import { ts } from 'ts-morph';
4
+ import { LiteralUnion } from 'vtils/types';
5
+ import { type XHandler } from '../core/types';
6
+ /** 注释 */
7
+ type Comment = {
8
+ existing: boolean;
9
+ description: string;
10
+ tags: Map<LiteralUnion<'private', string>, string>;
11
+ };
12
+ /** 接口传输数据 */
13
+ type ApiDto = {
6
14
  name: string;
7
15
  desc: string;
8
16
  type: 'string' | 'number' | 'boolean' | 'object' | 'array' | 'file';
9
17
  enum: any[];
10
18
  required: boolean;
11
- children: ApiData[];
12
- }
13
- type ApiHandles = Array<{
19
+ children: ApiDto[];
20
+ };
21
+ /** 接口数据 */
22
+ type ApiData = {
14
23
  category: string;
15
24
  name: string;
16
25
  path: string;
17
26
  handlerMethod: XHandler.Method;
18
27
  method: string;
19
- requestData: ApiData;
20
- responseData: ApiData;
28
+ requestData: ApiDto;
29
+ responseData: ApiDto;
21
30
  requestDataJsonSchema: JSONSchema4;
22
31
  responseDataJsonSchema: JSONSchema4;
23
- }>;
32
+ };
24
33
  export declare class ApiGenerator {
34
+ private cwd;
35
+ constructor(cwd?: string);
25
36
  debug: createDebug.Debugger;
26
- cwd: string;
27
- project: ts.Project;
28
- getTypeBySymbol(symbol: ts.Symbol): ts.Type;
29
- getComment(declaration?: ts.Node): {
30
- existing: boolean;
31
- description: string;
32
- tags: Map<string, string>;
33
- };
34
- getCommentBySymbol(symbol: ts.Symbol): string;
35
- typeToApiData(type: ts.Type, _symbol?: ts.Symbol): ApiData;
36
- apiDataToJsonSchema(apiData: ApiData, jsonSchema?: JSONSchema4): JSONSchema4;
37
- genYApiData(handles: ApiHandles): {
37
+ categoryUrlRe: RegExp;
38
+ checker: ts.TypeChecker;
39
+ start(): Promise<void>;
40
+ getComment(symbol?: ts.Symbol): Comment;
41
+ getApiDto({ type, keySymbol, }: {
42
+ type: ts.Type;
43
+ keySymbol?: ts.Symbol;
44
+ }): ApiDto;
45
+ apiDataToJsonSchema(apiData: ApiDto, jsonSchema?: JSONSchema4): JSONSchema4;
46
+ genYApiData(handles: ApiData[]): {
38
47
  name: string;
39
48
  desc: string;
40
49
  list: any;
41
50
  }[];
42
- generate(): Promise<void>;
43
51
  }
44
52
  export {};