@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 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
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
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,3 @@
1
+ import commander from 'commander';
2
+ declare const parse: commander.Command;
3
+ export default 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,4 @@
1
+ export declare const CONFIG_FILE_NAMES: string[];
2
+ export declare const DOC_SOURCE_SWAGGER: (server: string) => string;
3
+ export declare const DOC_SOURCE_APIFOX: (projectId: string) => string;
4
+ export declare const MOCK_SERVICE_APIFOX: (projectId: string) => string;
@@ -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,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,2 @@
1
+ declare const _default: import("axios").AxiosInstance;
2
+ export default _default;
@@ -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
@@ -0,0 +1,5 @@
1
+ import { generateApis } from "./core/generate";
2
+ import { defineConfig } from "./core/utils";
3
+ import request from "./core/request";
4
+ export { GenerateConfig } from "./core/interface";
5
+ export { generateApis, request, defineConfig };
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 }) %>