@jayfong/x-server 2.34.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.
- package/lib/_cjs/cli/api_generator.js +49 -2
- package/lib/_cjs/cli/cli.js +0 -3
- package/lib/_cjs/cli/templates/models.ts +25 -25
- package/lib/cli/api_generator.d.ts +15 -1
- package/lib/cli/api_generator.js +49 -2
- package/lib/cli/cli.js +0 -3
- package/lib/cli/templates/models.ts +25 -25
- package/package.json +7 -10
- package/lib/_cjs/cli/api_generator1.js +0 -286
- package/lib/cli/api_generator1.d.ts +0 -44
- package/lib/cli/api_generator1.js +0 -279
|
@@ -136,7 +136,9 @@ class ApiGenerator {
|
|
|
136
136
|
this.debug('写入文件...');
|
|
137
137
|
await Promise.all([_fsExtra.default.outputJSON(_path.default.join(this.cwd, 'temp/api.json'), apiData, {
|
|
138
138
|
spaces: 2
|
|
139
|
-
}), _fsExtra.default.outputJSON(_path.default.join(this.cwd, 'temp/yapi.json'), this.
|
|
139
|
+
}), _fsExtra.default.outputJSON(_path.default.join(this.cwd, 'temp/yapi.json'), this.apiDataToYApiData(apiData), {
|
|
140
|
+
spaces: 2
|
|
141
|
+
}), _fsExtra.default.outputJSON(_path.default.join(this.cwd, 'temp/openapi31.json'), this.apiDataToOpenAPI31Data(apiData), {
|
|
140
142
|
spaces: 2
|
|
141
143
|
})]);
|
|
142
144
|
}
|
|
@@ -281,7 +283,7 @@ class ApiGenerator {
|
|
|
281
283
|
}
|
|
282
284
|
return jsonSchema;
|
|
283
285
|
}
|
|
284
|
-
|
|
286
|
+
apiDataToYApiData(handles) {
|
|
285
287
|
const data = {};
|
|
286
288
|
for (const handle of handles) {
|
|
287
289
|
for (const path of handle.path) {
|
|
@@ -344,5 +346,50 @@ class ApiGenerator {
|
|
|
344
346
|
list: data[cat]
|
|
345
347
|
}));
|
|
346
348
|
}
|
|
349
|
+
apiDataToOpenAPI31Data(handles) {
|
|
350
|
+
const data = {
|
|
351
|
+
openapi: '3.1.0',
|
|
352
|
+
info: {
|
|
353
|
+
title: `${process.env.APP_NAME}`,
|
|
354
|
+
version: '1.0.0'
|
|
355
|
+
},
|
|
356
|
+
paths: {},
|
|
357
|
+
servers: [{
|
|
358
|
+
url: `${process.env.APP_URL}`,
|
|
359
|
+
description: '生产'
|
|
360
|
+
}, {
|
|
361
|
+
url: `http://127.0.0.1:${process.env.APP_PORT}/`,
|
|
362
|
+
description: '本地'
|
|
363
|
+
}]
|
|
364
|
+
};
|
|
365
|
+
for (const handle of handles) {
|
|
366
|
+
for (const path of handle.path) {
|
|
367
|
+
data.paths[path] = {
|
|
368
|
+
[handle.method.toLowerCase()]: {
|
|
369
|
+
tags: [handle.category],
|
|
370
|
+
summary: handle.name,
|
|
371
|
+
requestBody: {
|
|
372
|
+
content: {
|
|
373
|
+
'application/json': {
|
|
374
|
+
schema: handle.requestDataJsonSchema
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
},
|
|
378
|
+
responses: {
|
|
379
|
+
'200': {
|
|
380
|
+
description: 'success',
|
|
381
|
+
content: {
|
|
382
|
+
'application/json': {
|
|
383
|
+
schema: handle.responseDataJsonSchema
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
};
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
return data;
|
|
393
|
+
}
|
|
347
394
|
}
|
|
348
395
|
exports.ApiGenerator = ApiGenerator;
|
package/lib/_cjs/cli/cli.js
CHANGED
|
@@ -8,7 +8,6 @@ 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");
|
|
12
11
|
var _build_util = require("./build_util");
|
|
13
12
|
var _deploy_util = require("./deploy_util");
|
|
14
13
|
var _dev_util = require("./dev_util");
|
|
@@ -124,8 +123,6 @@ _yargs.default.command('dev', '开始开发', _ => _.positional('index', {
|
|
|
124
123
|
console.log('构建成功');
|
|
125
124
|
}).command('api', '生成 API', async () => {
|
|
126
125
|
await new _api_generator.ApiGenerator().start();
|
|
127
|
-
}).command('api1', '(old) 生成 API', async () => {
|
|
128
|
-
await new _api_generator2.ApiGenerator1().generate();
|
|
129
126
|
}).command('prisma', 'prisma 代理,主要为了注入环境变量', _ => _.positional('production', {
|
|
130
127
|
alias: 'p',
|
|
131
128
|
describe: '是否生产模式',
|
|
@@ -1,31 +1,29 @@
|
|
|
1
|
-
import { x
|
|
2
|
-
import {
|
|
1
|
+
import { x } from '@jayfong/x-server';
|
|
2
|
+
import { Prisma, PrismaClient } from '@prisma/client';
|
|
3
3
|
|
|
4
|
-
export const prismaClient = new PrismaClient(
|
|
5
|
-
rejectOnNotFound: err => new HttpError.NotFound(err.message),
|
|
6
|
-
})
|
|
4
|
+
export const prismaClient = new PrismaClient();
|
|
7
5
|
|
|
8
|
-
x.dispose.add(() => prismaClient.$disconnect())
|
|
6
|
+
x.dispose.add(() => prismaClient.$disconnect());
|
|
9
7
|
|
|
10
8
|
type ModelName =
|
|
11
9
|
// @index('../.prisma/client/index.d.ts', /(?<=const ModelName:).+?(?=\})/s, /(\S+?):/g, (m, _) => `| '${_.camel(m[1])}'`)
|
|
12
|
-
''
|
|
10
|
+
'';
|
|
13
11
|
// @endindex
|
|
14
12
|
|
|
15
13
|
function makeBaseModel<TModelName extends ModelName>(name: TModelName) {
|
|
16
14
|
return class BaseModel {
|
|
17
|
-
public query = prismaClient[name]
|
|
15
|
+
public query = prismaClient[name];
|
|
18
16
|
|
|
19
|
-
public transactionClient: Prisma.TransactionClient | null = null
|
|
17
|
+
public transactionClient: Prisma.TransactionClient | null = null;
|
|
20
18
|
|
|
21
19
|
public transactionify = (tx: Prisma.TransactionClient) => {
|
|
22
20
|
// @ts-ignore
|
|
23
21
|
return new (class extends this.constructor {
|
|
24
|
-
public query = tx[name]
|
|
22
|
+
public query = tx[name];
|
|
25
23
|
|
|
26
|
-
public transactionClient = tx
|
|
27
|
-
})() as typeof this
|
|
28
|
-
}
|
|
24
|
+
public transactionClient = tx;
|
|
25
|
+
})() as typeof this;
|
|
26
|
+
};
|
|
29
27
|
|
|
30
28
|
public getModel = <T extends ModelName>(
|
|
31
29
|
name: T,
|
|
@@ -34,27 +32,29 @@ function makeBaseModel<TModelName extends ModelName>(name: TModelName) {
|
|
|
34
32
|
? require('../../src/models')[`${name}Model`].transactionify(
|
|
35
33
|
this.transactionClient,
|
|
36
34
|
)
|
|
37
|
-
: require('../../src/models')[`${name}Model`]
|
|
38
|
-
}
|
|
39
|
-
}
|
|
35
|
+
: require('../../src/models')[`${name}Model`];
|
|
36
|
+
};
|
|
37
|
+
};
|
|
40
38
|
}
|
|
41
39
|
|
|
42
40
|
// @index('../.prisma/client/index.d.ts', /(?<=const ModelName:).+?(?=\})/s, /(\S+?):/g, (m, _) => `export const ${m[1]}BaseModel = makeBaseModel('${_.camel(m[1])}')`)
|
|
43
41
|
// @endindex
|
|
44
42
|
|
|
45
|
-
export * from '@prisma/client'
|
|
43
|
+
export * from '@prisma/client';
|
|
46
44
|
|
|
47
45
|
export const startTransaction = <T>(
|
|
48
|
-
cb: (
|
|
49
|
-
|
|
50
|
-
|
|
46
|
+
cb: (
|
|
47
|
+
models: {
|
|
48
|
+
[K in ModelName]: typeof import('../../src/models')[`${K}Model`];
|
|
49
|
+
},
|
|
50
|
+
) => Promise<T>,
|
|
51
51
|
): Promise<T> => {
|
|
52
52
|
return prismaClient.$transaction(async tx => {
|
|
53
|
-
const _models = require('../../src/models')
|
|
54
|
-
const cache = Object.create(null)
|
|
53
|
+
const _models = require('../../src/models');
|
|
54
|
+
const cache = Object.create(null);
|
|
55
55
|
return cb({
|
|
56
56
|
// @index('../.prisma/client/index.d.ts', /(?<=const ModelName:).+?(?=\})/s, /(\S+?):/g, (m, _) => `get ${_.camel(m[1])}() { return cache.${_.camel(m[1])} || (cache.${_.camel(m[1])} = _models.${_.camel(m[1])}Model.transactionify(tx)) }, `)
|
|
57
57
|
// @endindex
|
|
58
|
-
})
|
|
59
|
-
})
|
|
60
|
-
}
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
};
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import createDebug from 'debug';
|
|
2
2
|
import { type JSONSchema4 } from 'json-schema';
|
|
3
|
+
import { type OpenAPIV3_1 } from 'openapi-types';
|
|
3
4
|
import { ts } from 'ts-morph';
|
|
4
5
|
import { LiteralUnion } from 'vtils/types';
|
|
5
6
|
import { type XHandler } from '../core/types';
|
|
@@ -43,10 +44,23 @@ export declare class ApiGenerator {
|
|
|
43
44
|
keySymbol?: ts.Symbol;
|
|
44
45
|
}): ApiDto;
|
|
45
46
|
apiDataToJsonSchema(apiData: ApiDto, jsonSchema?: JSONSchema4): JSONSchema4;
|
|
46
|
-
|
|
47
|
+
apiDataToYApiData(handles: ApiData[]): {
|
|
47
48
|
name: string;
|
|
48
49
|
desc: string;
|
|
49
50
|
list: any;
|
|
50
51
|
}[];
|
|
52
|
+
apiDataToOpenAPI31Data(handles: ApiData[]): Omit<Omit<import("openapi-types").OpenAPIV3.Document<{}>, "paths" | "components">, "info" | "paths" | "components" | "servers" | "webhooks" | "jsonSchemaDialect"> & {
|
|
53
|
+
info: OpenAPIV3_1.InfoObject;
|
|
54
|
+
jsonSchemaDialect?: string;
|
|
55
|
+
servers?: OpenAPIV3_1.ServerObject[];
|
|
56
|
+
} & Pick<{
|
|
57
|
+
paths: OpenAPIV3_1.PathsObject<{}, {}>;
|
|
58
|
+
webhooks: Record<string, OpenAPIV3_1.ReferenceObject | OpenAPIV3_1.PathItemObject<{}>>;
|
|
59
|
+
components: OpenAPIV3_1.ComponentsObject;
|
|
60
|
+
}, "paths"> & Omit<Partial<{
|
|
61
|
+
paths: OpenAPIV3_1.PathsObject<{}, {}>;
|
|
62
|
+
webhooks: Record<string, OpenAPIV3_1.ReferenceObject | OpenAPIV3_1.PathItemObject<{}>>;
|
|
63
|
+
components: OpenAPIV3_1.ComponentsObject;
|
|
64
|
+
}>, "paths">;
|
|
51
65
|
}
|
|
52
66
|
export {};
|
package/lib/cli/api_generator.js
CHANGED
|
@@ -131,7 +131,9 @@ export class ApiGenerator {
|
|
|
131
131
|
this.debug('写入文件...');
|
|
132
132
|
await Promise.all([fs.outputJSON(path.join(this.cwd, 'temp/api.json'), apiData, {
|
|
133
133
|
spaces: 2
|
|
134
|
-
}), fs.outputJSON(path.join(this.cwd, 'temp/yapi.json'), this.
|
|
134
|
+
}), fs.outputJSON(path.join(this.cwd, 'temp/yapi.json'), this.apiDataToYApiData(apiData), {
|
|
135
|
+
spaces: 2
|
|
136
|
+
}), fs.outputJSON(path.join(this.cwd, 'temp/openapi31.json'), this.apiDataToOpenAPI31Data(apiData), {
|
|
135
137
|
spaces: 2
|
|
136
138
|
})]);
|
|
137
139
|
}
|
|
@@ -276,7 +278,7 @@ export class ApiGenerator {
|
|
|
276
278
|
}
|
|
277
279
|
return jsonSchema;
|
|
278
280
|
}
|
|
279
|
-
|
|
281
|
+
apiDataToYApiData(handles) {
|
|
280
282
|
const data = {};
|
|
281
283
|
for (const handle of handles) {
|
|
282
284
|
for (const path of handle.path) {
|
|
@@ -339,4 +341,49 @@ export class ApiGenerator {
|
|
|
339
341
|
list: data[cat]
|
|
340
342
|
}));
|
|
341
343
|
}
|
|
344
|
+
apiDataToOpenAPI31Data(handles) {
|
|
345
|
+
const data = {
|
|
346
|
+
openapi: '3.1.0',
|
|
347
|
+
info: {
|
|
348
|
+
title: `${process.env.APP_NAME}`,
|
|
349
|
+
version: '1.0.0'
|
|
350
|
+
},
|
|
351
|
+
paths: {},
|
|
352
|
+
servers: [{
|
|
353
|
+
url: `${process.env.APP_URL}`,
|
|
354
|
+
description: '生产'
|
|
355
|
+
}, {
|
|
356
|
+
url: `http://127.0.0.1:${process.env.APP_PORT}/`,
|
|
357
|
+
description: '本地'
|
|
358
|
+
}]
|
|
359
|
+
};
|
|
360
|
+
for (const handle of handles) {
|
|
361
|
+
for (const path of handle.path) {
|
|
362
|
+
data.paths[path] = {
|
|
363
|
+
[handle.method.toLowerCase()]: {
|
|
364
|
+
tags: [handle.category],
|
|
365
|
+
summary: handle.name,
|
|
366
|
+
requestBody: {
|
|
367
|
+
content: {
|
|
368
|
+
'application/json': {
|
|
369
|
+
schema: handle.requestDataJsonSchema
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
},
|
|
373
|
+
responses: {
|
|
374
|
+
'200': {
|
|
375
|
+
description: 'success',
|
|
376
|
+
content: {
|
|
377
|
+
'application/json': {
|
|
378
|
+
schema: handle.responseDataJsonSchema
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
};
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
return data;
|
|
388
|
+
}
|
|
342
389
|
}
|
package/lib/cli/cli.js
CHANGED
|
@@ -5,7 +5,6 @@ import { generateManyIndex } from 'vscode-generate-index-standalone';
|
|
|
5
5
|
import { castArray, debounce } from 'vtils';
|
|
6
6
|
import yargs from 'yargs';
|
|
7
7
|
import { ApiGenerator } from "./api_generator";
|
|
8
|
-
import { ApiGenerator1 } from "./api_generator1";
|
|
9
8
|
import { BuildUtil } from "./build_util";
|
|
10
9
|
import { DeployUtil } from "./deploy_util";
|
|
11
10
|
import { DevUtil } from "./dev_util";
|
|
@@ -121,8 +120,6 @@ yargs.command('dev', '开始开发', _ => _.positional('index', {
|
|
|
121
120
|
console.log('构建成功');
|
|
122
121
|
}).command('api', '生成 API', async () => {
|
|
123
122
|
await new ApiGenerator().start();
|
|
124
|
-
}).command('api1', '(old) 生成 API', async () => {
|
|
125
|
-
await new ApiGenerator1().generate();
|
|
126
123
|
}).command('prisma', 'prisma 代理,主要为了注入环境变量', _ => _.positional('production', {
|
|
127
124
|
alias: 'p',
|
|
128
125
|
describe: '是否生产模式',
|
|
@@ -1,31 +1,29 @@
|
|
|
1
|
-
import { x
|
|
2
|
-
import {
|
|
1
|
+
import { x } from '@jayfong/x-server';
|
|
2
|
+
import { Prisma, PrismaClient } from '@prisma/client';
|
|
3
3
|
|
|
4
|
-
export const prismaClient = new PrismaClient(
|
|
5
|
-
rejectOnNotFound: err => new HttpError.NotFound(err.message),
|
|
6
|
-
})
|
|
4
|
+
export const prismaClient = new PrismaClient();
|
|
7
5
|
|
|
8
|
-
x.dispose.add(() => prismaClient.$disconnect())
|
|
6
|
+
x.dispose.add(() => prismaClient.$disconnect());
|
|
9
7
|
|
|
10
8
|
type ModelName =
|
|
11
9
|
// @index('../.prisma/client/index.d.ts', /(?<=const ModelName:).+?(?=\})/s, /(\S+?):/g, (m, _) => `| '${_.camel(m[1])}'`)
|
|
12
|
-
''
|
|
10
|
+
'';
|
|
13
11
|
// @endindex
|
|
14
12
|
|
|
15
13
|
function makeBaseModel<TModelName extends ModelName>(name: TModelName) {
|
|
16
14
|
return class BaseModel {
|
|
17
|
-
public query = prismaClient[name]
|
|
15
|
+
public query = prismaClient[name];
|
|
18
16
|
|
|
19
|
-
public transactionClient: Prisma.TransactionClient | null = null
|
|
17
|
+
public transactionClient: Prisma.TransactionClient | null = null;
|
|
20
18
|
|
|
21
19
|
public transactionify = (tx: Prisma.TransactionClient) => {
|
|
22
20
|
// @ts-ignore
|
|
23
21
|
return new (class extends this.constructor {
|
|
24
|
-
public query = tx[name]
|
|
22
|
+
public query = tx[name];
|
|
25
23
|
|
|
26
|
-
public transactionClient = tx
|
|
27
|
-
})() as typeof this
|
|
28
|
-
}
|
|
24
|
+
public transactionClient = tx;
|
|
25
|
+
})() as typeof this;
|
|
26
|
+
};
|
|
29
27
|
|
|
30
28
|
public getModel = <T extends ModelName>(
|
|
31
29
|
name: T,
|
|
@@ -34,27 +32,29 @@ function makeBaseModel<TModelName extends ModelName>(name: TModelName) {
|
|
|
34
32
|
? require('../../src/models')[`${name}Model`].transactionify(
|
|
35
33
|
this.transactionClient,
|
|
36
34
|
)
|
|
37
|
-
: require('../../src/models')[`${name}Model`]
|
|
38
|
-
}
|
|
39
|
-
}
|
|
35
|
+
: require('../../src/models')[`${name}Model`];
|
|
36
|
+
};
|
|
37
|
+
};
|
|
40
38
|
}
|
|
41
39
|
|
|
42
40
|
// @index('../.prisma/client/index.d.ts', /(?<=const ModelName:).+?(?=\})/s, /(\S+?):/g, (m, _) => `export const ${m[1]}BaseModel = makeBaseModel('${_.camel(m[1])}')`)
|
|
43
41
|
// @endindex
|
|
44
42
|
|
|
45
|
-
export * from '@prisma/client'
|
|
43
|
+
export * from '@prisma/client';
|
|
46
44
|
|
|
47
45
|
export const startTransaction = <T>(
|
|
48
|
-
cb: (
|
|
49
|
-
|
|
50
|
-
|
|
46
|
+
cb: (
|
|
47
|
+
models: {
|
|
48
|
+
[K in ModelName]: typeof import('../../src/models')[`${K}Model`];
|
|
49
|
+
},
|
|
50
|
+
) => Promise<T>,
|
|
51
51
|
): Promise<T> => {
|
|
52
52
|
return prismaClient.$transaction(async tx => {
|
|
53
|
-
const _models = require('../../src/models')
|
|
54
|
-
const cache = Object.create(null)
|
|
53
|
+
const _models = require('../../src/models');
|
|
54
|
+
const cache = Object.create(null);
|
|
55
55
|
return cb({
|
|
56
56
|
// @index('../.prisma/client/index.d.ts', /(?<=const ModelName:).+?(?=\})/s, /(\S+?):/g, (m, _) => `get ${_.camel(m[1])}() { return cache.${_.camel(m[1])} || (cache.${_.camel(m[1])} = _models.${_.camel(m[1])}Model.transactionify(tx)) }, `)
|
|
57
57
|
// @endindex
|
|
58
|
-
})
|
|
59
|
-
})
|
|
60
|
-
}
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jayfong/x-server",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.35.0",
|
|
4
4
|
"license": "ISC",
|
|
5
5
|
"sideEffects": false,
|
|
6
6
|
"main": "lib/_cjs/index.js",
|
|
@@ -9,9 +9,7 @@
|
|
|
9
9
|
"bin": {
|
|
10
10
|
"xs": "lib/_cjs/cli/cli.js"
|
|
11
11
|
},
|
|
12
|
-
"files": [
|
|
13
|
-
"lib"
|
|
14
|
-
],
|
|
12
|
+
"files": ["lib"],
|
|
15
13
|
"scripts": {
|
|
16
14
|
"build": "haoma compile",
|
|
17
15
|
"build_test_pkg": "tyn build && rm -rf ./lib_test && mkdir -p ./lib_test && cp -r ./lib ./lib_test/lib && cp ./package.json ./lib_test/package.json && cd ./tests/app && tyn add file:../../lib_test",
|
|
@@ -19,16 +17,14 @@
|
|
|
19
17
|
"test": "tsc --noEmit -p ./tsconfig.build.json && jest \"$(pwd)/src/\"",
|
|
20
18
|
"test_all": "tsc --noEmit -p ./tsconfig.build.json && jest",
|
|
21
19
|
"test_api_bot": "cd /Users/admin/Documents/jfWorks/qiqi-bot && DEBUG=api tsx /Users/admin/Documents/jfWorks/x/packages/x-server/src/cli/cli.ts api",
|
|
22
|
-
"test_api_qiqi": "cd /Users/admin/Documents/jfWorks/qiqi-server && DEBUG=api tsx /Users/admin/Documents/jfWorks/x/packages/x-server/src/cli/cli.ts api"
|
|
23
|
-
"test_api1_bot": "cd /Users/admin/Documents/jfWorks/qiqi-bot && DEBUG=api1 tsx /Users/admin/Documents/jfWorks/x/packages/x-server/src/cli/cli.ts api1",
|
|
24
|
-
"test_api1_qiqi": "cd /Users/admin/Documents/jfWorks/qiqi-server && DEBUG=api1 tsx /Users/admin/Documents/jfWorks/x/packages/x-server/src/cli/cli.ts api1"
|
|
20
|
+
"test_api_qiqi": "cd /Users/admin/Documents/jfWorks/qiqi-server && DEBUG=api tsx /Users/admin/Documents/jfWorks/x/packages/x-server/src/cli/cli.ts api"
|
|
25
21
|
},
|
|
26
22
|
"dependencies": {
|
|
27
23
|
"@fastify/cors": "^8.3.0",
|
|
28
24
|
"@fastify/formbody": "^7.4.0",
|
|
29
25
|
"@fastify/multipart": "^7.7.0",
|
|
30
26
|
"@fastify/websocket": "^8.1.0",
|
|
31
|
-
"@prisma/client": "^
|
|
27
|
+
"@prisma/client": "^5.8.0",
|
|
32
28
|
"@types/busboy": "^0.3.2",
|
|
33
29
|
"@types/http-errors": "^1.8.2",
|
|
34
30
|
"@types/jsonwebtoken": "^8.5.8",
|
|
@@ -62,7 +58,7 @@
|
|
|
62
58
|
"nodemailer": "^6.7.3",
|
|
63
59
|
"pino-pretty": "^10.0.1",
|
|
64
60
|
"pirates": "^4.0.6",
|
|
65
|
-
"prisma": "^
|
|
61
|
+
"prisma": "^5.8.0",
|
|
66
62
|
"select-run": "^1.1.2",
|
|
67
63
|
"supports-color": "^8",
|
|
68
64
|
"svg-captcha": "^1.4.0",
|
|
@@ -85,7 +81,8 @@
|
|
|
85
81
|
"ioredis-mock": "^8.7.0",
|
|
86
82
|
"json-xml-parse": "^1.2.4",
|
|
87
83
|
"node-fetch": "^3.3.1",
|
|
88
|
-
"npm-check-updates": "^12.5.9"
|
|
84
|
+
"npm-check-updates": "^12.5.9",
|
|
85
|
+
"openapi-types": "^12.1.3"
|
|
89
86
|
},
|
|
90
87
|
"publishConfig": {
|
|
91
88
|
"access": "public"
|
|
@@ -1,286 +0,0 @@
|
|
|
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;
|
|
@@ -1,44 +0,0 @@
|
|
|
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 {
|
|
6
|
-
name: string;
|
|
7
|
-
desc: string;
|
|
8
|
-
type: 'string' | 'number' | 'boolean' | 'object' | 'array' | 'file';
|
|
9
|
-
enum: any[];
|
|
10
|
-
required: boolean;
|
|
11
|
-
children: ApiData[];
|
|
12
|
-
}
|
|
13
|
-
type ApiHandles = Array<{
|
|
14
|
-
category: string;
|
|
15
|
-
name: string;
|
|
16
|
-
path: string;
|
|
17
|
-
handlerMethod: XHandler.Method;
|
|
18
|
-
method: string;
|
|
19
|
-
requestData: ApiData;
|
|
20
|
-
responseData: ApiData;
|
|
21
|
-
requestDataJsonSchema: JSONSchema4;
|
|
22
|
-
responseDataJsonSchema: JSONSchema4;
|
|
23
|
-
}>;
|
|
24
|
-
export declare class ApiGenerator1 {
|
|
25
|
-
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): {
|
|
38
|
-
name: string;
|
|
39
|
-
desc: string;
|
|
40
|
-
list: any;
|
|
41
|
-
}[];
|
|
42
|
-
generate(): Promise<void>;
|
|
43
|
-
}
|
|
44
|
-
export {};
|
|
@@ -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()
|