@wx-sab/renkei 0.2.7
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/README.md +194 -0
- package/lib/cli.d.ts +2 -0
- package/lib/cli.js +56 -0
- package/lib/commands/api.d.ts +3 -0
- package/lib/commands/api.js +34 -0
- package/lib/core/constants.d.ts +4 -0
- package/lib/core/constants.js +14 -0
- package/lib/core/generate.d.ts +9 -0
- package/lib/core/generate.js +149 -0
- package/lib/core/interface.d.ts +76 -0
- package/lib/core/interface.js +2 -0
- package/lib/core/request.d.ts +2 -0
- package/lib/core/request.js +7 -0
- package/lib/core/utils.d.ts +21 -0
- package/lib/core/utils.js +89 -0
- package/lib/index.d.ts +5 -0
- package/lib/index.js +12 -0
- package/package.json +37 -0
- package/templates/api.ejs +102 -0
- package/templates/data-contracts.ejs +54 -0
- package/templates/procedure-call.ejs +172 -0
- package/templates/route-docs.ejs +31 -0
- package/templates/service.ejs +34 -0
package/README.md
ADDED
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
# @wx-sab/renkei
|
|
2
|
+
|
|
3
|
+
解析 swagger 转为 ts 代码,生成可立即使用的 ts 代码,无需手动书写接口定义
|
|
4
|
+
|
|
5
|
+
> 日本动漫《游戏王》中的‘Renkei’,意为连携或协作。
|
|
6
|
+
|
|
7
|
+
## Feature
|
|
8
|
+
|
|
9
|
+
- [x] 支持 `swagger` 解析
|
|
10
|
+
- [x] 支持 `apifox` 导出 `swagger` 解析
|
|
11
|
+
- [x] 支持多项目/多服务配置
|
|
12
|
+
- [x] 支持远程 `apifox` 远程 Mock
|
|
13
|
+
- [x] 多服务的 apifox 项目解析
|
|
14
|
+
- [x] 支持 swagger 3.0 解析
|
|
15
|
+
|
|
16
|
+
## Introduce
|
|
17
|
+
|
|
18
|
+
### Install
|
|
19
|
+
|
|
20
|
+
项目发布在私有的 `npm源`,故需要更改 npm 源地址为:[http://10.10.200.19:4873/](http://10.10.200.19:4873/)(临时的)
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
npm install @wx-sab/renkei --registry http://10.10.200.19:4873
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
> 注:可以使用 nrm 管理 npm 源
|
|
27
|
+
|
|
28
|
+
### Configuration
|
|
29
|
+
|
|
30
|
+
```ts
|
|
31
|
+
import { GenerateApiParams, RawRouteInfo } from "swagger-typescript-api";
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* 基础配置
|
|
35
|
+
*/
|
|
36
|
+
export type BasicConfig = {
|
|
37
|
+
/**
|
|
38
|
+
* 内置接口文档来源
|
|
39
|
+
* swagger: `http://10.101.16.30:31809/${server}/v2/api-docs`
|
|
40
|
+
* apifox: `http://127.0.0.1:4523/export/openapi?projectId=${projectId}&version=2.0`
|
|
41
|
+
*/
|
|
42
|
+
sourceType?: "swagger" | "apifox";
|
|
43
|
+
/**
|
|
44
|
+
* 自定义接口文档来源,与 sourceType 互斥,优先级比 sourceType 高
|
|
45
|
+
*/
|
|
46
|
+
source?: string;
|
|
47
|
+
/**
|
|
48
|
+
* 声明文件输出目录, 默认 src/services
|
|
49
|
+
*/
|
|
50
|
+
output?: string;
|
|
51
|
+
/**
|
|
52
|
+
* 配置文件url, 默认读取项目根目录下的 renkei.config.js 或者 renkei.config.ts
|
|
53
|
+
*/
|
|
54
|
+
configUrl?: string;
|
|
55
|
+
/**
|
|
56
|
+
* 数据模型的命名空间
|
|
57
|
+
*/
|
|
58
|
+
modelNamespace?: string;
|
|
59
|
+
/**
|
|
60
|
+
* 接口服务名
|
|
61
|
+
*/
|
|
62
|
+
serviceName: string;
|
|
63
|
+
/**
|
|
64
|
+
* 可以生成多个项目,会继承父配置项
|
|
65
|
+
*/
|
|
66
|
+
projects?: GenerateConfig[];
|
|
67
|
+
/**
|
|
68
|
+
* 自定义 request 引入模板
|
|
69
|
+
* 默认:import request from '${pkg.name}/lib/request'
|
|
70
|
+
*/
|
|
71
|
+
requestTempalte?: string;
|
|
72
|
+
/**
|
|
73
|
+
* 自定义响应类型,默认为 AxiosResponse<T>
|
|
74
|
+
*/
|
|
75
|
+
customResponseType?(type: string): string;
|
|
76
|
+
/**
|
|
77
|
+
* 自定义组装请求的url
|
|
78
|
+
* 自定义组装 mock 的url
|
|
79
|
+
*/
|
|
80
|
+
customRequestUrl?(routeData: RawRouteInfo): string | {
|
|
81
|
+
/**
|
|
82
|
+
* 接口请求路径
|
|
83
|
+
*/
|
|
84
|
+
path: string;
|
|
85
|
+
/**
|
|
86
|
+
* mock请求完整 url
|
|
87
|
+
*/
|
|
88
|
+
mockUrl?: string
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* 如果 sourceType 是 apifox,则适用该配置项
|
|
93
|
+
*/
|
|
94
|
+
export type ApifoxConfig = {
|
|
95
|
+
/**
|
|
96
|
+
* apifox 项目ID
|
|
97
|
+
*/
|
|
98
|
+
apifoxProjectId?: string;
|
|
99
|
+
/**
|
|
100
|
+
* 自定义 mock 服务地址
|
|
101
|
+
*/
|
|
102
|
+
customMockService?(config: GenerateConfig): string
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* 配置项
|
|
107
|
+
*/
|
|
108
|
+
export type GenerateConfig = Partial<GenerateApiParams> &
|
|
109
|
+
BasicConfig & ApifoxConfig;
|
|
110
|
+
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
### Useage
|
|
114
|
+
|
|
115
|
+
```ts
|
|
116
|
+
import { defineConfig } from "@wx-sab/renkei";
|
|
117
|
+
|
|
118
|
+
export default defineConfig([
|
|
119
|
+
// apifox 单服务项目
|
|
120
|
+
{
|
|
121
|
+
// 服务名
|
|
122
|
+
serviceName: "dp-order",
|
|
123
|
+
// 项目ID
|
|
124
|
+
apifoxProjectId: "2963311",
|
|
125
|
+
},
|
|
126
|
+
// apifox 多服务项目
|
|
127
|
+
{
|
|
128
|
+
// 服务名
|
|
129
|
+
serviceName: "dp-service",
|
|
130
|
+
// 项目ID
|
|
131
|
+
apifoxProjectId: "2954843",
|
|
132
|
+
// 自定义 request 引入
|
|
133
|
+
requestTempalte: `import { request } from '@/utils/http'`,
|
|
134
|
+
// 自定义组装请求url
|
|
135
|
+
customRequestUrl: (routeData) => {
|
|
136
|
+
return (
|
|
137
|
+
"/" +
|
|
138
|
+
// @ts-ignore
|
|
139
|
+
routeData["x-apifox-folder"].split("/")[0] +
|
|
140
|
+
// @ts-ignore
|
|
141
|
+
routeData.route
|
|
142
|
+
);
|
|
143
|
+
},
|
|
144
|
+
},
|
|
145
|
+
// swagger项目
|
|
146
|
+
{
|
|
147
|
+
sourceType: 'swagger',
|
|
148
|
+
serviceName: 'dp-product',
|
|
149
|
+
},
|
|
150
|
+
// 自定义 swagger 源
|
|
151
|
+
{
|
|
152
|
+
source: 'https://swagger.com/xxx/xxx',
|
|
153
|
+
serviceName: 'custom-swagger'
|
|
154
|
+
}
|
|
155
|
+
]);
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
## Examples
|
|
159
|
+
|
|
160
|
+
### 自定义接口的 mock 服务
|
|
161
|
+
```ts
|
|
162
|
+
import { request } from '@/utils/request'
|
|
163
|
+
import { DpService } from './services/dp-service'
|
|
164
|
+
|
|
165
|
+
const mockProxy = {
|
|
166
|
+
"/dp-order": {
|
|
167
|
+
mockService: "https://mock.apifox.cn/m1/2954843-0-6f0df579",
|
|
168
|
+
},
|
|
169
|
+
"/dp-product": {
|
|
170
|
+
mockService: "https://mock.apifox.cn/m1/2954843-0-4d5b1ae0",
|
|
171
|
+
},
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
generateApis({
|
|
175
|
+
// 服务名
|
|
176
|
+
serviceName: "dp-service",
|
|
177
|
+
// 项目ID
|
|
178
|
+
apifoxProjectId: "2954843",
|
|
179
|
+
// 自定义 request 引入
|
|
180
|
+
requestTempalte: `import request from "../../core/request";`,
|
|
181
|
+
// 自定义组装请求url
|
|
182
|
+
customRequestUrl: (routeData) => {
|
|
183
|
+
const prefix = "/" + routeData["x-apifox-folder"].split("/")[0];
|
|
184
|
+
const path = prefix + routeData.route;
|
|
185
|
+
|
|
186
|
+
return {
|
|
187
|
+
path,
|
|
188
|
+
// @ts-ignore
|
|
189
|
+
mockUrl: mockProxy[prefix]?.mockService ? mockProxy[prefix]?.mockService + routeData.route : undefined,
|
|
190
|
+
};
|
|
191
|
+
},
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
```
|
package/lib/cli.d.ts
ADDED
package/lib/cli.js
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
4
|
+
if (k2 === undefined) k2 = k;
|
|
5
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
6
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
7
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
8
|
+
}
|
|
9
|
+
Object.defineProperty(o, k2, desc);
|
|
10
|
+
}) : (function(o, m, k, k2) {
|
|
11
|
+
if (k2 === undefined) k2 = k;
|
|
12
|
+
o[k2] = m[k];
|
|
13
|
+
}));
|
|
14
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
15
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
16
|
+
}) : function(o, v) {
|
|
17
|
+
o["default"] = v;
|
|
18
|
+
});
|
|
19
|
+
var __importStar = (this && this.__importStar) || function (mod) {
|
|
20
|
+
if (mod && mod.__esModule) return mod;
|
|
21
|
+
var result = {};
|
|
22
|
+
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
|
23
|
+
__setModuleDefault(result, mod);
|
|
24
|
+
return result;
|
|
25
|
+
};
|
|
26
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
27
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
28
|
+
};
|
|
29
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
30
|
+
const commander_1 = __importDefault(require("commander"));
|
|
31
|
+
const TSNode = __importStar(require("ts-node"));
|
|
32
|
+
const api_1 = __importDefault(require("./commands/api"));
|
|
33
|
+
TSNode.register({
|
|
34
|
+
// 不加载本地的 tsconfig.json
|
|
35
|
+
skipProject: true,
|
|
36
|
+
// 仅转译,不做类型检查
|
|
37
|
+
transpileOnly: true,
|
|
38
|
+
// 自定义编译选项
|
|
39
|
+
compilerOptions: {
|
|
40
|
+
strict: false,
|
|
41
|
+
target: "es6",
|
|
42
|
+
module: "commonjs",
|
|
43
|
+
moduleResolution: "node",
|
|
44
|
+
declaration: false,
|
|
45
|
+
removeComments: false,
|
|
46
|
+
esModuleInterop: true,
|
|
47
|
+
allowSyntheticDefaultImports: true,
|
|
48
|
+
importHelpers: false,
|
|
49
|
+
allowJs: true,
|
|
50
|
+
lib: ["esnext"],
|
|
51
|
+
},
|
|
52
|
+
});
|
|
53
|
+
commander_1.default.program
|
|
54
|
+
.version(require("../package.json").version, "-v, --version")
|
|
55
|
+
.addCommand(api_1.default)
|
|
56
|
+
.parse();
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
const fs_1 = __importDefault(require("fs"));
|
|
7
|
+
const path_1 = __importDefault(require("path"));
|
|
8
|
+
const commander_1 = __importDefault(require("commander"));
|
|
9
|
+
const generate_1 = require("../core/generate");
|
|
10
|
+
const constants_1 = require("../core/constants");
|
|
11
|
+
/**
|
|
12
|
+
* 读取项目中的配置文件
|
|
13
|
+
*/
|
|
14
|
+
const readProjectConfigFile = () => {
|
|
15
|
+
for (const file of constants_1.CONFIG_FILE_NAMES) {
|
|
16
|
+
const fullFilePath = path_1.default.resolve(process.cwd(), file);
|
|
17
|
+
if (fs_1.default.existsSync(fullFilePath)) {
|
|
18
|
+
if (fullFilePath.endsWith('.ts')) {
|
|
19
|
+
return require(fullFilePath).default;
|
|
20
|
+
}
|
|
21
|
+
return require(fullFilePath);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
return null;
|
|
25
|
+
};
|
|
26
|
+
const parse = new commander_1.default.Command('api').action(() => {
|
|
27
|
+
const config = readProjectConfigFile();
|
|
28
|
+
if (!config) {
|
|
29
|
+
console.warn('未找到配置文件');
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
(0, generate_1.generateApis)(config);
|
|
33
|
+
});
|
|
34
|
+
exports.default = parse;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.MOCK_SERVICE_APIFOX = exports.DOC_SOURCE_APIFOX = exports.DOC_SOURCE_SWAGGER = exports.CONFIG_FILE_NAMES = void 0;
|
|
4
|
+
// 读取项目的配置文件名
|
|
5
|
+
exports.CONFIG_FILE_NAMES = ['renkei.config.js', 'renkei.config.ts'];
|
|
6
|
+
// 内置 swagger 文档源
|
|
7
|
+
const DOC_SOURCE_SWAGGER = (server) => `http://10.101.16.30:30600/${server}/v2/api-docs`;
|
|
8
|
+
exports.DOC_SOURCE_SWAGGER = DOC_SOURCE_SWAGGER;
|
|
9
|
+
// 内置 apifox 文档源
|
|
10
|
+
const DOC_SOURCE_APIFOX = (projectId) => `http://127.0.0.1:4523/export/openapi?projectId=${projectId}&version=2.0`;
|
|
11
|
+
exports.DOC_SOURCE_APIFOX = DOC_SOURCE_APIFOX;
|
|
12
|
+
// 内置 apifox mock 服务
|
|
13
|
+
const MOCK_SERVICE_APIFOX = (projectId) => `https://mock.apifox.cn/m1/${projectId}-0-default`;
|
|
14
|
+
exports.MOCK_SERVICE_APIFOX = MOCK_SERVICE_APIFOX;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 生成接口模型和请求
|
|
3
|
+
*/
|
|
4
|
+
import { GenerateConfig } from "./interface";
|
|
5
|
+
/**
|
|
6
|
+
* 单个配置生成
|
|
7
|
+
*/
|
|
8
|
+
export declare const generate: (conf: GenerateConfig) => Promise<import("swagger-typescript-api").GenerateApiOutput>;
|
|
9
|
+
export declare const generateApis: (config: GenerateConfig | GenerateConfig[]) => Promise<void>;
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* 生成接口模型和请求
|
|
4
|
+
*/
|
|
5
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
6
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
7
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
8
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
9
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
10
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
11
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
12
|
+
});
|
|
13
|
+
};
|
|
14
|
+
var __asyncValues = (this && this.__asyncValues) || function (o) {
|
|
15
|
+
if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined.");
|
|
16
|
+
var m = o[Symbol.asyncIterator], i;
|
|
17
|
+
return m ? m.call(o) : (o = typeof __values === "function" ? __values(o) : o[Symbol.iterator](), i = {}, verb("next"), verb("throw"), verb("return"), i[Symbol.asyncIterator] = function () { return this; }, i);
|
|
18
|
+
function verb(n) { i[n] = o[n] && function (v) { return new Promise(function (resolve, reject) { v = o[n](v), settle(resolve, reject, v.done, v.value); }); }; }
|
|
19
|
+
function settle(resolve, reject, d, v) { Promise.resolve(v).then(function(v) { resolve({ value: v, done: d }); }, reject); }
|
|
20
|
+
};
|
|
21
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
22
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
23
|
+
};
|
|
24
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
25
|
+
exports.generateApis = exports.generate = void 0;
|
|
26
|
+
const path_1 = __importDefault(require("path"));
|
|
27
|
+
const lodash_1 = __importDefault(require("lodash"));
|
|
28
|
+
const swagger_typescript_api_1 = require("swagger-typescript-api");
|
|
29
|
+
const utils_1 = require("./utils");
|
|
30
|
+
/**
|
|
31
|
+
* 单个配置生成
|
|
32
|
+
*/
|
|
33
|
+
const generate = (conf) => {
|
|
34
|
+
// 最终使用的配置项
|
|
35
|
+
const config = (0, utils_1.generateConfig)(conf);
|
|
36
|
+
// 模型命名空间
|
|
37
|
+
const modelNamespace = lodash_1.default.upperFirst(lodash_1.default.camelCase(config.modelNamespace));
|
|
38
|
+
// 服务名
|
|
39
|
+
const serviceName = lodash_1.default.upperFirst(lodash_1.default.camelCase(config.serviceName));
|
|
40
|
+
// 存放已生成的方法名
|
|
41
|
+
const methodNameSet = new Set();
|
|
42
|
+
return (0, swagger_typescript_api_1.generateApi)({
|
|
43
|
+
url: config.source,
|
|
44
|
+
// 文件名
|
|
45
|
+
name: config.name,
|
|
46
|
+
// 输出目录
|
|
47
|
+
output: path_1.default.resolve(config.output, config.serviceName),
|
|
48
|
+
// 模块化?
|
|
49
|
+
modular: false,
|
|
50
|
+
extractRequestParams: true,
|
|
51
|
+
// 清空输出目录?
|
|
52
|
+
cleanOutput: false,
|
|
53
|
+
// 生成请求客户端
|
|
54
|
+
generateClient: false,
|
|
55
|
+
// 模板文件
|
|
56
|
+
templates: path_1.default.resolve(__dirname, "../../templates"),
|
|
57
|
+
// 额外的,此处为了生成接口
|
|
58
|
+
extraTemplates: [
|
|
59
|
+
{
|
|
60
|
+
name: "index.ts",
|
|
61
|
+
path: path_1.default.resolve(__dirname, "../../templates/service.ejs"),
|
|
62
|
+
},
|
|
63
|
+
],
|
|
64
|
+
hooks: {
|
|
65
|
+
onCreateRouteName(routeNameInfo, rawRouteInfo) {
|
|
66
|
+
var _a;
|
|
67
|
+
// 自定义请求路由
|
|
68
|
+
const newRoute = (_a = config.customRequestUrl) === null || _a === void 0 ? void 0 : _a.call(config, rawRouteInfo);
|
|
69
|
+
if (typeof newRoute === "string") {
|
|
70
|
+
rawRouteInfo.route = newRoute;
|
|
71
|
+
}
|
|
72
|
+
else if (newRoute === null || newRoute === void 0 ? void 0 : newRoute.path) {
|
|
73
|
+
rawRouteInfo.route = newRoute.path;
|
|
74
|
+
// @ts-ignore
|
|
75
|
+
rawRouteInfo.mockUrl = newRoute.mockUrl;
|
|
76
|
+
}
|
|
77
|
+
const methodName = (0, utils_1.generateRouteName)(rawRouteInfo.method, rawRouteInfo.route);
|
|
78
|
+
const duplicate = methodNameSet.has(methodName);
|
|
79
|
+
methodNameSet.add(methodName);
|
|
80
|
+
return {
|
|
81
|
+
duplicate: duplicate,
|
|
82
|
+
original: methodName,
|
|
83
|
+
usage: methodName,
|
|
84
|
+
};
|
|
85
|
+
},
|
|
86
|
+
onPrepareConfig(currentConfiguration) {
|
|
87
|
+
return Object.assign(Object.assign({}, currentConfiguration), { modelNamespace,
|
|
88
|
+
serviceName, requestTempalte: config.requestTempalte });
|
|
89
|
+
},
|
|
90
|
+
onParseSchema(originalSchema, parsedSchema) {
|
|
91
|
+
// 针对解析出的 any 做转换,主要是 swagger 3.0
|
|
92
|
+
if (parsedSchema.name === "any" &&
|
|
93
|
+
typeof parsedSchema.content === "string" &&
|
|
94
|
+
parsedSchema.$ref) {
|
|
95
|
+
return Object.assign(Object.assign({}, parsedSchema), { content: `${modelNamespace}.${serviceName}.${(0, utils_1.getValidName)(decodeURIComponent(parsedSchema.$ref))}` });
|
|
96
|
+
}
|
|
97
|
+
return parsedSchema;
|
|
98
|
+
},
|
|
99
|
+
onCreateRoute(routeData) {
|
|
100
|
+
var _a;
|
|
101
|
+
// 将 /api/v1/order-mains/{orderId} 转成 /api/v1/order-mains/${orderId}
|
|
102
|
+
// routeData.raw.route 是进过 customRequestUrl 转换过的
|
|
103
|
+
// @ts-ignore
|
|
104
|
+
routeData.request.path = (_a = routeData.raw.route) === null || _a === void 0 ? void 0 : _a.replace(/\{([^}]+)\}/g, '${$1}');
|
|
105
|
+
return routeData;
|
|
106
|
+
},
|
|
107
|
+
onInit(configuration) {
|
|
108
|
+
return Object.assign(Object.assign({}, configuration), { serviceName: config.serviceName, apifoxProjectId: config.apifoxProjectId, customResponseType: config.customResponseType, customMockService: config.customMockService });
|
|
109
|
+
},
|
|
110
|
+
},
|
|
111
|
+
});
|
|
112
|
+
};
|
|
113
|
+
exports.generate = generate;
|
|
114
|
+
// 批量生成接口
|
|
115
|
+
const generateApis = (config) => __awaiter(void 0, void 0, void 0, function* () {
|
|
116
|
+
var _a, e_1, _b, _c;
|
|
117
|
+
// 递归收集config下可能有的 projects ,也当做新的config
|
|
118
|
+
const collectConfig = (conf, collects = []) => {
|
|
119
|
+
if (!conf)
|
|
120
|
+
return collects;
|
|
121
|
+
if (Array.isArray(conf)) {
|
|
122
|
+
conf.forEach((con) => collectConfig(con, collects));
|
|
123
|
+
}
|
|
124
|
+
else if (Array.isArray(conf.projects)) {
|
|
125
|
+
conf.projects.forEach((project) => collectConfig(Object.assign(Object.assign({}, conf), project), collects));
|
|
126
|
+
}
|
|
127
|
+
else {
|
|
128
|
+
collects.push(conf);
|
|
129
|
+
}
|
|
130
|
+
return collects;
|
|
131
|
+
};
|
|
132
|
+
const _configs = collectConfig(config);
|
|
133
|
+
try {
|
|
134
|
+
for (var _d = true, _configs_1 = __asyncValues(_configs), _configs_1_1; _configs_1_1 = yield _configs_1.next(), _a = _configs_1_1.done, !_a; _d = true) {
|
|
135
|
+
_c = _configs_1_1.value;
|
|
136
|
+
_d = false;
|
|
137
|
+
const conf = _c;
|
|
138
|
+
yield (0, exports.generate)(conf);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
catch (e_1_1) { e_1 = { error: e_1_1 }; }
|
|
142
|
+
finally {
|
|
143
|
+
try {
|
|
144
|
+
if (!_d && !_a && (_b = _configs_1.return)) yield _b.call(_configs_1);
|
|
145
|
+
}
|
|
146
|
+
finally { if (e_1) throw e_1.error; }
|
|
147
|
+
}
|
|
148
|
+
});
|
|
149
|
+
exports.generateApis = generateApis;
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { GenerateApiParams, RawRouteInfo } from "swagger-typescript-api";
|
|
2
|
+
/**
|
|
3
|
+
* 基础配置
|
|
4
|
+
*/
|
|
5
|
+
export type BasicConfig = {
|
|
6
|
+
/**
|
|
7
|
+
* 内置接口文档来源
|
|
8
|
+
* swagger: `http://10.101.16.30:31809/${server}/v2/api-docs`
|
|
9
|
+
* apifox: `http://127.0.0.1:4523/export/openapi?projectId=${projectId}&version=2.0`
|
|
10
|
+
*/
|
|
11
|
+
sourceType?: "swagger" | "apifox";
|
|
12
|
+
/**
|
|
13
|
+
* 自定义接口文档来源,与 sourceType 互斥,优先级比 sourceType 高
|
|
14
|
+
*/
|
|
15
|
+
source?: string;
|
|
16
|
+
/**
|
|
17
|
+
* 声明文件输出目录, 默认 src/services
|
|
18
|
+
*/
|
|
19
|
+
output?: string;
|
|
20
|
+
/**
|
|
21
|
+
* 配置文件url, 默认读取项目根目录下的 renkei.config.js 或者 renkei.config.ts
|
|
22
|
+
*/
|
|
23
|
+
configUrl?: string;
|
|
24
|
+
/**
|
|
25
|
+
* 数据模型的命名空间
|
|
26
|
+
*/
|
|
27
|
+
modelNamespace?: string;
|
|
28
|
+
/**
|
|
29
|
+
* 接口服务名
|
|
30
|
+
*/
|
|
31
|
+
serviceName: string;
|
|
32
|
+
/**
|
|
33
|
+
* 可以生成多个项目,会继承父配置项
|
|
34
|
+
*/
|
|
35
|
+
projects?: GenerateConfig[];
|
|
36
|
+
/**
|
|
37
|
+
* 自定义 request 引入模板
|
|
38
|
+
* 默认:import request from '${pkg.name}/lib/request'
|
|
39
|
+
*/
|
|
40
|
+
requestTempalte?: string;
|
|
41
|
+
/**
|
|
42
|
+
* 自定义响应类型,默认为 AxiosResponse<T>
|
|
43
|
+
*/
|
|
44
|
+
customResponseType?(type: string): string;
|
|
45
|
+
/**
|
|
46
|
+
* 自定义组装请求的url
|
|
47
|
+
* 自定义组装 mock 的url
|
|
48
|
+
*/
|
|
49
|
+
customRequestUrl?(routeData: RawRouteInfo): string | {
|
|
50
|
+
/**
|
|
51
|
+
* 接口请求路径
|
|
52
|
+
*/
|
|
53
|
+
path: string;
|
|
54
|
+
/**
|
|
55
|
+
* mock请求完整 url
|
|
56
|
+
*/
|
|
57
|
+
mockUrl?: string;
|
|
58
|
+
};
|
|
59
|
+
};
|
|
60
|
+
/**
|
|
61
|
+
* 如果 sourceType 是 apifox,则适用该配置项
|
|
62
|
+
*/
|
|
63
|
+
export type ApifoxConfig = {
|
|
64
|
+
/**
|
|
65
|
+
* apifox 项目ID
|
|
66
|
+
*/
|
|
67
|
+
apifoxProjectId?: string;
|
|
68
|
+
/**
|
|
69
|
+
* 自定义 mock 服务地址
|
|
70
|
+
*/
|
|
71
|
+
customMockService?(config: GenerateConfig): string;
|
|
72
|
+
};
|
|
73
|
+
/**
|
|
74
|
+
* 配置项
|
|
75
|
+
*/
|
|
76
|
+
export type GenerateConfig = Partial<GenerateApiParams> & BasicConfig & ApifoxConfig;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
const axios_1 = __importDefault(require("axios"));
|
|
7
|
+
exports.default = axios_1.default.create();
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { GenerateConfig } from "./interface";
|
|
2
|
+
/**
|
|
3
|
+
* 生成最终使用的配置参数
|
|
4
|
+
*/
|
|
5
|
+
export declare const generateConfig: (conf: Partial<GenerateConfig>) => Partial<GenerateConfig>;
|
|
6
|
+
/**
|
|
7
|
+
* 生成方法名
|
|
8
|
+
*/
|
|
9
|
+
export declare const generateRouteName: (method: string, route: string) => string;
|
|
10
|
+
/**
|
|
11
|
+
* 辅助配置文件
|
|
12
|
+
* @param configs
|
|
13
|
+
* @returns
|
|
14
|
+
*/
|
|
15
|
+
export declare const defineConfig: (configs: GenerateConfig | GenerateConfig[]) => GenerateConfig | GenerateConfig[];
|
|
16
|
+
/**
|
|
17
|
+
* 过滤嵌套情况 ResponseVO«AgentTraceVO»
|
|
18
|
+
* @param content
|
|
19
|
+
* @returns
|
|
20
|
+
*/
|
|
21
|
+
export declare function getValidName(content: string): string;
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.getValidName = exports.defineConfig = exports.generateRouteName = exports.generateConfig = void 0;
|
|
7
|
+
const lodash_1 = __importDefault(require("lodash"));
|
|
8
|
+
const path_1 = __importDefault(require("path"));
|
|
9
|
+
const constants_1 = require("./constants");
|
|
10
|
+
/**
|
|
11
|
+
* 生成最终使用的配置参数
|
|
12
|
+
*/
|
|
13
|
+
const generateConfig = (conf) => {
|
|
14
|
+
const DEFAULT_CONFIG = {
|
|
15
|
+
// 默认从 apifox 生成
|
|
16
|
+
sourceType: 'apifox',
|
|
17
|
+
// 默认为项目根目录下 src/services
|
|
18
|
+
output: path_1.default.resolve(process.cwd(), "./src/services"),
|
|
19
|
+
// 生成的文件名
|
|
20
|
+
name: "model.d.ts",
|
|
21
|
+
// 模块化?
|
|
22
|
+
modular: false,
|
|
23
|
+
// 数据模型 命名空间
|
|
24
|
+
modelNamespace: 'Model',
|
|
25
|
+
// 默认引入 request 模板
|
|
26
|
+
requestTempalte: `import { request } from '@wx-sab/renkei'`,
|
|
27
|
+
// 自定义响应类型
|
|
28
|
+
customResponseType: type => `AxiosResponse<${type}>`,
|
|
29
|
+
// 自定义mock服务
|
|
30
|
+
customMockService: config => config.apifoxProjectId ? (0, constants_1.MOCK_SERVICE_APIFOX)(config.apifoxProjectId) : ''
|
|
31
|
+
};
|
|
32
|
+
const finalConfig = lodash_1.default.merge(DEFAULT_CONFIG, conf);
|
|
33
|
+
// 内置文档源
|
|
34
|
+
if (!finalConfig.source) {
|
|
35
|
+
// apifox 来源
|
|
36
|
+
if (finalConfig.sourceType === 'apifox') {
|
|
37
|
+
finalConfig.source = (0, constants_1.DOC_SOURCE_APIFOX)(finalConfig.apifoxProjectId);
|
|
38
|
+
}
|
|
39
|
+
// swagger 来源
|
|
40
|
+
else if (finalConfig.sourceType === 'swagger') {
|
|
41
|
+
finalConfig.source = (0, constants_1.DOC_SOURCE_SWAGGER)(finalConfig.serviceName);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return finalConfig;
|
|
45
|
+
};
|
|
46
|
+
exports.generateConfig = generateConfig;
|
|
47
|
+
/**
|
|
48
|
+
* 生成方法名
|
|
49
|
+
*/
|
|
50
|
+
const generateRouteName = (method, route) => {
|
|
51
|
+
return [
|
|
52
|
+
method.toLowerCase(),
|
|
53
|
+
...route
|
|
54
|
+
.split("/")
|
|
55
|
+
// 全部转成小写
|
|
56
|
+
.map((path) => path.toLowerCase())
|
|
57
|
+
// 转义 url 中的变量, {variable} -> $p
|
|
58
|
+
.map((path) => path.replace(/^\{(.+)\}$/, "$p"))
|
|
59
|
+
// 中横线转成下划线
|
|
60
|
+
.map((path) => path.replace(/[-*.]/g, "_"))
|
|
61
|
+
.filter(Boolean),
|
|
62
|
+
].join("_");
|
|
63
|
+
};
|
|
64
|
+
exports.generateRouteName = generateRouteName;
|
|
65
|
+
/**
|
|
66
|
+
* 辅助配置文件
|
|
67
|
+
* @param configs
|
|
68
|
+
* @returns
|
|
69
|
+
*/
|
|
70
|
+
const defineConfig = (configs) => {
|
|
71
|
+
return configs;
|
|
72
|
+
};
|
|
73
|
+
exports.defineConfig = defineConfig;
|
|
74
|
+
/**
|
|
75
|
+
* 过滤嵌套情况 ResponseVO«AgentTraceVO»
|
|
76
|
+
* @param content
|
|
77
|
+
* @returns
|
|
78
|
+
*/
|
|
79
|
+
function getValidName(content) {
|
|
80
|
+
if (!content)
|
|
81
|
+
return null;
|
|
82
|
+
if (content.match(/\W/)) {
|
|
83
|
+
const contents = content.split("/");
|
|
84
|
+
const last = contents[contents.length - 1];
|
|
85
|
+
return last.replace(/\W/g, "");
|
|
86
|
+
}
|
|
87
|
+
return content;
|
|
88
|
+
}
|
|
89
|
+
exports.getValidName = getValidName;
|
package/lib/index.d.ts
ADDED
package/lib/index.js
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.defineConfig = exports.request = exports.generateApis = void 0;
|
|
7
|
+
const generate_1 = require("./core/generate");
|
|
8
|
+
Object.defineProperty(exports, "generateApis", { enumerable: true, get: function () { return generate_1.generateApis; } });
|
|
9
|
+
const utils_1 = require("./core/utils");
|
|
10
|
+
Object.defineProperty(exports, "defineConfig", { enumerable: true, get: function () { return utils_1.defineConfig; } });
|
|
11
|
+
const request_1 = __importDefault(require("./core/request"));
|
|
12
|
+
exports.request = request_1.default;
|
package/package.json
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@wx-sab/renkei",
|
|
3
|
+
"version": "0.2.7",
|
|
4
|
+
"description": "解析swagger转为ts代码,日本动漫《游戏王》中的‘Renkei’,意为连携或协作。",
|
|
5
|
+
"main": "./lib/index.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"renkei": "./lib/cli.js"
|
|
8
|
+
},
|
|
9
|
+
"scripts": {
|
|
10
|
+
"dev": "ts-node ./src/index.ts",
|
|
11
|
+
"demo": "ts-node ./demo.ts",
|
|
12
|
+
"clean": "rm -rf lib",
|
|
13
|
+
"build": "npm run clean & tsc --outDir lib"
|
|
14
|
+
},
|
|
15
|
+
"repository": {
|
|
16
|
+
"type": "git",
|
|
17
|
+
"url": "http://10.10.255.105/fe/api-to-ts.git"
|
|
18
|
+
},
|
|
19
|
+
"author": "sab-fe",
|
|
20
|
+
"license": "ISC",
|
|
21
|
+
"devDependencies": {
|
|
22
|
+
"@types/lodash": "^4.14.196",
|
|
23
|
+
"@types/node": "^20.4.7",
|
|
24
|
+
"typescript": "^5.1.6"
|
|
25
|
+
},
|
|
26
|
+
"files": [
|
|
27
|
+
"lib",
|
|
28
|
+
"templates"
|
|
29
|
+
],
|
|
30
|
+
"dependencies": {
|
|
31
|
+
"axios": "^1.4.0",
|
|
32
|
+
"commander": "^11.0.0",
|
|
33
|
+
"lodash": "^4.17.21",
|
|
34
|
+
"swagger-typescript-api": "^12.0.4",
|
|
35
|
+
"ts-node": "^10.9.1"
|
|
36
|
+
}
|
|
37
|
+
}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
<%
|
|
2
|
+
const { apiConfig, routes, utils, config, requestTemplate, modelNamespace, serviceName, namespace, requestTempalte, modelTypes } = it;
|
|
3
|
+
const { info, servers, externalDocs } = apiConfig;
|
|
4
|
+
const { _, require, formatDescription } = utils;
|
|
5
|
+
|
|
6
|
+
const server = (servers && servers[0]) || { url: "" };
|
|
7
|
+
|
|
8
|
+
const descriptionLines = _.compact([
|
|
9
|
+
`@title ${info.title || "No title"}`,
|
|
10
|
+
info.version && `@version ${info.version}`,
|
|
11
|
+
info.license && `@license ${_.compact([
|
|
12
|
+
info.license.name,
|
|
13
|
+
info.license.url && `(${info.license.url})`,
|
|
14
|
+
]).join(" ")}`,
|
|
15
|
+
info.termsOfService && `@termsOfService ${info.termsOfService}`,
|
|
16
|
+
server.url && `@baseUrl ${server.url}`,
|
|
17
|
+
externalDocs.url && `@externalDocs ${externalDocs.url}`,
|
|
18
|
+
info.contact && `@contact ${_.compact([
|
|
19
|
+
info.contact.name,
|
|
20
|
+
info.contact.email && `<${info.contact.email}>`,
|
|
21
|
+
info.contact.url && `(${info.contact.url})`,
|
|
22
|
+
]).join(" ")}`,
|
|
23
|
+
info.description && " ",
|
|
24
|
+
info.description && _.replace(formatDescription(info.description), /\n/g, "\n * "),
|
|
25
|
+
]);
|
|
26
|
+
|
|
27
|
+
%>
|
|
28
|
+
|
|
29
|
+
// @ts-nocheck
|
|
30
|
+
|
|
31
|
+
<% if (descriptionLines.length) { %>
|
|
32
|
+
/**
|
|
33
|
+
<% descriptionLines.forEach((descriptionLine) => { %>
|
|
34
|
+
* <%~ descriptionLine %>
|
|
35
|
+
|
|
36
|
+
<% }) %>
|
|
37
|
+
*/
|
|
38
|
+
<% } %>
|
|
39
|
+
|
|
40
|
+
import { AxiosResponse, AxiosRequestConfig } from 'axios'
|
|
41
|
+
import type { <%~ modelNamespace %> } from './model.d.ts'
|
|
42
|
+
|
|
43
|
+
// 自定义 request 模板
|
|
44
|
+
<%~ requestTempalte %>
|
|
45
|
+
|
|
46
|
+
type RequestConfig = AxiosRequestConfig & {
|
|
47
|
+
/**
|
|
48
|
+
* 开启mock
|
|
49
|
+
*/
|
|
50
|
+
mock?: boolean;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
enum ContentType {
|
|
54
|
+
Json = 'application/json',
|
|
55
|
+
FormData = 'multipart/form-data',
|
|
56
|
+
UrlEncoded = 'application/x-www-form-urlencoded',
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export class <%~ serviceName %> {
|
|
60
|
+
/**
|
|
61
|
+
* 常规 Mock 服务地址
|
|
62
|
+
*/
|
|
63
|
+
static private mockService = '<%~ config.customMockService?.(config) || '' %>';
|
|
64
|
+
static setMockService (service) {
|
|
65
|
+
<%~ serviceName %>.mockService = service;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* 开启Mock
|
|
70
|
+
*/
|
|
71
|
+
static private isMock = false;
|
|
72
|
+
static enableMock(enable?: boolean = true) {
|
|
73
|
+
<%~ serviceName %>.isMock = enable;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* 如果开启 mock, 替换请求的url
|
|
78
|
+
* @param {String} path 请求接口路径
|
|
79
|
+
* @param {Boolean} mock 是否开启 Mock
|
|
80
|
+
* @param {String} customMockUrl 自定义mock链接
|
|
81
|
+
*/
|
|
82
|
+
static generateUrl(params: {
|
|
83
|
+
path: string,
|
|
84
|
+
mock?: boolean,
|
|
85
|
+
customMockUrl?: string
|
|
86
|
+
}): string {
|
|
87
|
+
if (<%~ serviceName %>.isMock || params.mock) {
|
|
88
|
+
return params.customMockUrl || (<%~ serviceName %>.mockService + params.path)
|
|
89
|
+
}
|
|
90
|
+
return params.path;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
<% routes.combined && routes.combined.forEach(({ routes = [], moduleName }) => { %>
|
|
94
|
+
<% routes.forEach((route) => { %>
|
|
95
|
+
|
|
96
|
+
<%~ includeFile('./procedure-call.eta', { ...it, route }) %>
|
|
97
|
+
|
|
98
|
+
<% }) %>
|
|
99
|
+
<% }) %>
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export { <%~ modelNamespace %> }
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
<%
|
|
2
|
+
const { modelTypes, utils, config, namespace, modelNamespace, serviceName } = it;
|
|
3
|
+
const { enumRefs } = config
|
|
4
|
+
const { formatDescription, require, _ } = utils;
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
const dataContractTemplates = {
|
|
8
|
+
enum: (contract) => {
|
|
9
|
+
if (contract.typeData.relativePath) {
|
|
10
|
+
return `type ${contract.name} = ${contract.typeData.uniqueName};`;
|
|
11
|
+
}
|
|
12
|
+
return `enum ${contract.name} {\r\n${contract.content} \r\n }`;
|
|
13
|
+
},
|
|
14
|
+
interface: (contract) => {
|
|
15
|
+
return `interface ${contract.name} {\r\n${contract.content}}`;
|
|
16
|
+
},
|
|
17
|
+
type: (contract) => {
|
|
18
|
+
return `type ${contract.name} = ${contract.content}`;
|
|
19
|
+
},
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const createDescription = (contract) => {
|
|
23
|
+
if (!contract.typeData) return _.compact([contract.description]);
|
|
24
|
+
|
|
25
|
+
return _.compact([
|
|
26
|
+
contract.description && formatDescription(contract.description),
|
|
27
|
+
!_.isUndefined(contract.typeData.format) && `@format ${contract.typeData.format}`,
|
|
28
|
+
!_.isUndefined(contract.typeData.minimum) && `@min ${contract.typeData.minimum}`,
|
|
29
|
+
!_.isUndefined(contract.typeData.maximum) && `@max ${contract.typeData.maximum}`,
|
|
30
|
+
!_.isUndefined(contract.typeData.pattern) && `@pattern ${contract.typeData.pattern}`,
|
|
31
|
+
!_.isUndefined(contract.typeData.example) && `@example ${
|
|
32
|
+
_.isObject(contract.typeData.example) ? JSON.stringify(contract.typeData.example) : contract.typeData.example
|
|
33
|
+
}`,
|
|
34
|
+
]);
|
|
35
|
+
}
|
|
36
|
+
%>
|
|
37
|
+
|
|
38
|
+
export namespace <%~ modelNamespace %> {
|
|
39
|
+
export namespace <%~ serviceName %> {
|
|
40
|
+
<% modelTypes.forEach((contract) => { %>
|
|
41
|
+
<% const description = createDescription(contract); %>
|
|
42
|
+
|
|
43
|
+
<% if (description.length) { %>
|
|
44
|
+
/**
|
|
45
|
+
<%~ description.map(part => `* ${part}`).join("\n") %>
|
|
46
|
+
|
|
47
|
+
*/
|
|
48
|
+
<% } %>
|
|
49
|
+
<%~ (dataContractTemplates[contract.typeIdentifier] || dataContractTemplates.type)(contract) %>
|
|
50
|
+
|
|
51
|
+
<% }) %>
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
<%
|
|
2
|
+
const { utils, route, config, modelTypes, namespace, modelNamespace, serviceName, requestTempalte, rawModelTypes, strict, } = it;
|
|
3
|
+
const { requestBodyInfo, responseBodyInfo, specificArgNameResolver, routeParams, conflict, merged } = route;
|
|
4
|
+
const { _, getInlineParseContent, getParseContent, parseSchema, getComponentByRef, require, pick } = utils;
|
|
5
|
+
const { parameters, path, method, payload, query, formData, security, requestParams } = route.request;
|
|
6
|
+
const { type, errorType, contentTypes } = route.response;
|
|
7
|
+
const { HTTP_CLIENT, RESERVED_REQ_PARAMS_ARG_NAMES } = config.constants;
|
|
8
|
+
const routeDocs = includeFile("@custom/route-docs", { config, route, utils });
|
|
9
|
+
const queryName = (query && query.name) || "query";
|
|
10
|
+
const pathParams = _.values(parameters);
|
|
11
|
+
const pathParamsNames = _.map(pathParams, "name");
|
|
12
|
+
|
|
13
|
+
const isFetchTemplate = config.httpClientType === HTTP_CLIENT.FETCH;
|
|
14
|
+
|
|
15
|
+
const requestConfigParam = {
|
|
16
|
+
name: 'config',
|
|
17
|
+
optional: true,
|
|
18
|
+
type: "RequestConfig",
|
|
19
|
+
defaultValue: "{}",
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const getListItemType = (type) => type.replace(/\((\w+)\)\[\]/g, (match, key) => `${key}[]`)
|
|
23
|
+
|
|
24
|
+
const getFullType = (type) => {
|
|
25
|
+
const matchType = modelTypes.find(t => (t.name || t.type) === type)
|
|
26
|
+
if (!matchType) {
|
|
27
|
+
return type
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return `${modelNamespace}.${serviceName}.${type}`
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const getWrapperFullType = (type) => {
|
|
34
|
+
const fullType = getFullType(type)
|
|
35
|
+
const wrapper = config.customResponseType?.(fullType)
|
|
36
|
+
if (wrapper) {
|
|
37
|
+
return wrapper
|
|
38
|
+
}
|
|
39
|
+
return fullType
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const argToTmpl = ({ name, optional, type, defaultValue }) => `${name}${!defaultValue && optional ? '?' : ''}: ${getFullType(type)}${defaultValue ? ` = ${defaultValue}` : ''}`;
|
|
43
|
+
|
|
44
|
+
const rawWrapperArgs = config.extractRequestParams ?
|
|
45
|
+
_.compact([
|
|
46
|
+
requestParams && {
|
|
47
|
+
name: pathParams.length ? `{ ${_.join(pathParamsNames, ", ")}, ...${queryName} }` : queryName,
|
|
48
|
+
optional: false,
|
|
49
|
+
type: getInlineParseContent(requestParams),
|
|
50
|
+
},
|
|
51
|
+
...(!requestParams ? pathParams : []),
|
|
52
|
+
payload,
|
|
53
|
+
requestConfigParam,
|
|
54
|
+
]) :
|
|
55
|
+
_.compact([
|
|
56
|
+
...pathParams,
|
|
57
|
+
query,
|
|
58
|
+
payload,
|
|
59
|
+
requestConfigParam,
|
|
60
|
+
])
|
|
61
|
+
|
|
62
|
+
const wrapperArgs = _
|
|
63
|
+
// Sort by optionality
|
|
64
|
+
.sortBy(rawWrapperArgs, [o => o.optional])
|
|
65
|
+
.map(argToTmpl)
|
|
66
|
+
.join(', ')
|
|
67
|
+
|
|
68
|
+
// RequestParams["type"]
|
|
69
|
+
const requestContentKind = {
|
|
70
|
+
"JSON": "ContentType.Json",
|
|
71
|
+
"URL_ENCODED": "ContentType.UrlEncoded",
|
|
72
|
+
"FORM_DATA": "ContentType.FormData",
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// RequestParams["format"]
|
|
76
|
+
const responseContentKind = {
|
|
77
|
+
"JSON": '"json"',
|
|
78
|
+
"IMAGE": '"blob"',
|
|
79
|
+
"FORM_DATA": isFetchTemplate ? '"formData"' : '"document"'
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const bodyTmpl = _.get(payload, "name") || null;
|
|
83
|
+
const queryTmpl = (query != null && queryName) || null;
|
|
84
|
+
const bodyContentKindTmpl = requestContentKind[requestBodyInfo.contentKind] || null;
|
|
85
|
+
const responseFormatTmpl = responseContentKind[responseBodyInfo.success && responseBodyInfo.success.schema && responseBodyInfo.success.schema.contentKind] || null;
|
|
86
|
+
const securityTmpl = security ? 'true' : null;
|
|
87
|
+
|
|
88
|
+
const describeReturnType = () => {
|
|
89
|
+
if (!config.toJS) return "";
|
|
90
|
+
|
|
91
|
+
switch(config.httpClientType) {
|
|
92
|
+
case HTTP_CLIENT.AXIOS: {
|
|
93
|
+
return `Promise<AxiosResponse<${type}>>`
|
|
94
|
+
}
|
|
95
|
+
default: {
|
|
96
|
+
return `Promise<HttpResponse<${type}, ${errorType}>`
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const mergeInlineType = (schemas) => {
|
|
102
|
+
if (!schemas?.length) return null
|
|
103
|
+
const mergedType = schemas.reduce((acc, cur) => {
|
|
104
|
+
acc += `${cur.name}: ${cur.type};`
|
|
105
|
+
return acc
|
|
106
|
+
}, "")
|
|
107
|
+
|
|
108
|
+
return `{${mergedType}}`
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const dataTypes = [
|
|
112
|
+
requestParams && getInlineParseContent(requestParams),
|
|
113
|
+
mergeInlineType(!requestParams ? pathParams : []),
|
|
114
|
+
payload?.type,
|
|
115
|
+
].filter(Boolean)
|
|
116
|
+
|
|
117
|
+
const dataArgs = [
|
|
118
|
+
{
|
|
119
|
+
// name: 'data',
|
|
120
|
+
name: pathParams.length ? `{ ${_.join(pathParamsNames, ", ")}, ...${queryName} }` : 'data',
|
|
121
|
+
type: dataTypes.map(getFullType).join(' & '),
|
|
122
|
+
optional: false,
|
|
123
|
+
},
|
|
124
|
+
requestConfigParam,
|
|
125
|
+
].filter((item) => item.type)
|
|
126
|
+
|
|
127
|
+
const wrappedDataArgs = _
|
|
128
|
+
.sortBy(dataArgs, [o => o.optional])
|
|
129
|
+
.map(argToTmpl)
|
|
130
|
+
.join(', ')
|
|
131
|
+
|
|
132
|
+
const getTypeSchema = (type) => {
|
|
133
|
+
// Model.SalesService.IdParam => IdParam
|
|
134
|
+
const realType = (type || '').split('.').pop()
|
|
135
|
+
return rawModelTypes.find((item) => item.typeName === realType)?.rawTypeData?.properties
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const requestBodySchemaNames = Object.keys(getTypeSchema(requestBodyInfo?.type) || {}).map((name) => ({ name }))
|
|
139
|
+
|
|
140
|
+
%>
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
<%~ routeDocs.description %>
|
|
144
|
+
|
|
145
|
+
* <% /* Here you can add some other JSDoc tags */ %>
|
|
146
|
+
|
|
147
|
+
<%~ routeDocs.lines %>
|
|
148
|
+
|
|
149
|
+
<% if(merged) { %>
|
|
150
|
+
* @merged
|
|
151
|
+
<% } %>
|
|
152
|
+
<% if(conflict) { %>
|
|
153
|
+
* @conflict
|
|
154
|
+
<% } %>
|
|
155
|
+
*/
|
|
156
|
+
static public <%~ route.routeName.usage %> = async (<%~ wrappedDataArgs %>): Promise<<%~ getWrapperFullType(type) %>> => {
|
|
157
|
+
const url = <%~ serviceName %>.generateUrl({
|
|
158
|
+
path: `<%~ path %>`,
|
|
159
|
+
mock: config.mock,
|
|
160
|
+
customMockUrl: `<%~ route.raw.mockUrl || '' %>`
|
|
161
|
+
})
|
|
162
|
+
return request({
|
|
163
|
+
url,
|
|
164
|
+
method: '<%~ _.upperCase(method) %>',
|
|
165
|
+
<%~ queryTmpl ? `params: data,` : '' %>
|
|
166
|
+
<%~ bodyTmpl ? `data,` : '' %>
|
|
167
|
+
<%~ securityTmpl ? `secure: ${securityTmpl},` : '' %>
|
|
168
|
+
<%~ bodyContentKindTmpl ? `type: ${bodyContentKindTmpl},` : '' %>
|
|
169
|
+
<%~ responseFormatTmpl ? `format: ${responseFormatTmpl},` : '' %>
|
|
170
|
+
...<%~ _.get(requestConfigParam, "name") %>,
|
|
171
|
+
});
|
|
172
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
<%
|
|
2
|
+
const { config, route, utils } = it;
|
|
3
|
+
const { _, formatDescription, fmtToJSDocLine, classNameCase, require } = utils;
|
|
4
|
+
const { raw, request, routeName } = route;
|
|
5
|
+
|
|
6
|
+
const jsDocDescription = raw.description ?
|
|
7
|
+
` * @description ${formatDescription(raw.description, true)}` :
|
|
8
|
+
fmtToJSDocLine('No description', { eol: false });
|
|
9
|
+
const jsDocLines = _.compact([
|
|
10
|
+
_.size(raw.tags) && ` * @tags ${raw.tags.join(", ")}`,
|
|
11
|
+
` * @name ${classNameCase(routeName.usage)}`,
|
|
12
|
+
raw.summary && ` * @summary ${raw.summary}`,
|
|
13
|
+
` * @request ${_.upperCase(request.method)}:${raw.route}`,
|
|
14
|
+
raw.deprecated && ` * @deprecated`,
|
|
15
|
+
routeName.duplicate && ` * @originalName ${routeName.original}`,
|
|
16
|
+
routeName.duplicate && ` * @duplicate`,
|
|
17
|
+
request.security && ` * @secure`,
|
|
18
|
+
...(config.generateResponses && raw.responsesTypes.length
|
|
19
|
+
? raw.responsesTypes.map(
|
|
20
|
+
({ type, status, description, isSuccess }) =>
|
|
21
|
+
` * @response \`${status}\` \`${type}\` ${description}`,
|
|
22
|
+
)
|
|
23
|
+
: []),
|
|
24
|
+
]).join("\n");
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
return {
|
|
28
|
+
description: jsDocDescription,
|
|
29
|
+
lines: jsDocLines,
|
|
30
|
+
}
|
|
31
|
+
%>
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
<%
|
|
2
|
+
const { modelTypes, utils, config, namespace, serviceName } = it;
|
|
3
|
+
const { formatDescription, require, _ } = utils;
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
const dataContractTemplates = {
|
|
7
|
+
enum: (contract) => {
|
|
8
|
+
return `enum ${contract.name} {\r\n${contract.content} \r\n }`;
|
|
9
|
+
},
|
|
10
|
+
interface: (contract) => {
|
|
11
|
+
return `interface ${contract.name} {\r\n${contract.content}}`;
|
|
12
|
+
},
|
|
13
|
+
type: (contract) => {
|
|
14
|
+
return `type ${contract.name} = ${contract.content}`;
|
|
15
|
+
},
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const createDescription = (contract) => {
|
|
19
|
+
if (!contract.typeData) return _.compact([contract.description]);
|
|
20
|
+
|
|
21
|
+
return _.compact([
|
|
22
|
+
contract.description && formatDescription(contract.description),
|
|
23
|
+
!_.isUndefined(contract.typeData.format) && `@format ${contract.typeData.format}`,
|
|
24
|
+
!_.isUndefined(contract.typeData.minimum) && `@min ${contract.typeData.minimum}`,
|
|
25
|
+
!_.isUndefined(contract.typeData.maximum) && `@max ${contract.typeData.maximum}`,
|
|
26
|
+
!_.isUndefined(contract.typeData.pattern) && `@pattern ${contract.typeData.pattern}`,
|
|
27
|
+
!_.isUndefined(contract.typeData.example) && `@example ${
|
|
28
|
+
_.isObject(contract.typeData.example) ? JSON.stringify(contract.typeData.example) : contract.typeData.example
|
|
29
|
+
}`,
|
|
30
|
+
]);
|
|
31
|
+
}
|
|
32
|
+
%>
|
|
33
|
+
|
|
34
|
+
<%~ includeFile('./api.ejs', { ...it }) %>
|