swagger2api-v3 1.1.8 → 1.1.10
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 +6 -4
- package/dist/.swagger2api.schema.json +4 -0
- package/dist/cli/index.js +20 -19
- package/dist/config/validator.js +2 -0
- package/dist/core/generator.d.ts +18 -0
- package/dist/core/generator.js +73 -25
- package/dist/core/parser.d.ts +14 -0
- package/dist/core/parser.js +76 -54
- package/dist/index.js +22 -19
- package/dist/types/index.d.ts +8 -0
- package/dist/utils/comment.d.ts +18 -3
- package/dist/utils/comment.js +1 -1
- package/dist/utils/file.js +4 -3
- package/dist/utils/index.d.ts +1 -0
- package/dist/utils/index.js +1 -0
- package/dist/utils/logger.d.ts +115 -0
- package/dist/utils/logger.js +261 -0
- package/dist/utils/type.d.ts +25 -11
- package/dist/utils/type.js +37 -45
- package/package.json +23 -21
package/README.md
CHANGED
|
@@ -2,12 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
English | [中文](./README_CN.md)
|
|
4
4
|
|
|
5
|
-
A powerful command-line tool for automatically generating TypeScript or JavaScript interface code from OpenAPI 3.0 documentation.
|
|
5
|
+
A powerful command-line tool for automatically generating TypeScript or JavaScript interface code from OpenAPI 3.x / Swagger 3.0 documentation.
|
|
6
6
|
|
|
7
7
|
## ✨ Features
|
|
8
8
|
|
|
9
|
-
- 🚀 **Fast Generation** - Quickly generate TypeScript interface code from Swagger JSON
|
|
10
|
-
- 📁 **Smart Grouping** - Support automatic file grouping by
|
|
9
|
+
- 🚀 **Fast Generation** - Quickly generate TypeScript interface code from OpenAPI/Swagger 3.0 JSON
|
|
10
|
+
- 📁 **Smart Grouping** - Support automatic file grouping by document tags
|
|
11
11
|
- 📝 **Detailed Comments** - Automatically generate detailed comments including descriptions, parameters, and return values
|
|
12
12
|
- 🎨 **Code Formatting** - Support custom formatting commands
|
|
13
13
|
- ⚙️ **Environment Adaptation** - Automatically detect project environment and generate corresponding configuration files
|
|
@@ -44,6 +44,7 @@ The tool generates a `.swagger.config.json` configuration file:
|
|
|
44
44
|
"generator": "typescript",
|
|
45
45
|
"requestStyle": "generic",
|
|
46
46
|
"groupByTags": true,
|
|
47
|
+
"multiTagStrategy": "first",
|
|
47
48
|
"overwrite": true,
|
|
48
49
|
"prefix": "",
|
|
49
50
|
"lint": "prettier --write",
|
|
@@ -75,10 +76,11 @@ npx swagger2api-v3 generate
|
|
|
75
76
|
| Option | Type | Default | Description |
|
|
76
77
|
| ------------------------ | --------------------- | -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
77
78
|
| `$schema` | string | - | Local JSON Schema path for editor completion. The default points to `node_modules/swagger2api-v3/dist/.swagger2api.schema.json` |
|
|
78
|
-
| `input` | string | - | Swagger JSON file path or URL
|
|
79
|
+
| `input` | string | - | OpenAPI/Swagger 3.0 JSON file path or URL |
|
|
79
80
|
| `output` | string | `'./src/api'` | Output directory for generated code |
|
|
80
81
|
| `generator` | string | `'typescript'` | Code generator type. Supports `'typescript'` and `'javascript'`. `'javascript'` outputs `.js` files and skips type file generation |
|
|
81
82
|
| `groupByTags` | boolean | `true` | Whether to group files by tags |
|
|
83
|
+
| `multiTagStrategy` | 'first' \| 'all' | `'first'` | Grouping strategy for operations with multiple tags. `first` uses only the first tag, `all` combines all tags into one group name |
|
|
82
84
|
| `overwrite` | boolean | `true` | Whether to overwrite existing files |
|
|
83
85
|
| `prefix` | string | `''` | Common prefix for API paths |
|
|
84
86
|
| `importTemplate` | string | - | Import statement template for request function |
|
|
@@ -24,6 +24,10 @@
|
|
|
24
24
|
"type": "boolean",
|
|
25
25
|
"description": "是否按 tags 分组生成文件"
|
|
26
26
|
},
|
|
27
|
+
"multiTagStrategy": {
|
|
28
|
+
"enum": ["first", "all"],
|
|
29
|
+
"description": "多 tag 接口分组策略。first 只使用第一个 tag,all 将所有 tags 合成一个分组"
|
|
30
|
+
},
|
|
27
31
|
"overwrite": {
|
|
28
32
|
"type": "boolean",
|
|
29
33
|
"description": "生成前是否覆盖输出目录"
|
package/dist/cli/index.js
CHANGED
|
@@ -38,6 +38,7 @@ const commander_1 = require("commander");
|
|
|
38
38
|
const path = __importStar(require("path"));
|
|
39
39
|
const fs = __importStar(require("fs"));
|
|
40
40
|
const index_1 = require("../index");
|
|
41
|
+
const utils_1 = require("../utils");
|
|
41
42
|
const program = new commander_1.Command();
|
|
42
43
|
// 版本信息
|
|
43
44
|
const packageJson = JSON.parse(fs.readFileSync(path.join(__dirname, '../../package.json'), 'utf-8'));
|
|
@@ -73,7 +74,7 @@ program
|
|
|
73
74
|
prettify: true
|
|
74
75
|
}
|
|
75
76
|
};
|
|
76
|
-
const { generate } = await
|
|
77
|
+
const { generate } = await import('../index.js');
|
|
77
78
|
await generate(config);
|
|
78
79
|
}
|
|
79
80
|
else {
|
|
@@ -82,7 +83,7 @@ program
|
|
|
82
83
|
}
|
|
83
84
|
}
|
|
84
85
|
catch (error) {
|
|
85
|
-
|
|
86
|
+
utils_1.logger.error('生成失败', error);
|
|
86
87
|
process.exit(1);
|
|
87
88
|
}
|
|
88
89
|
});
|
|
@@ -98,11 +99,11 @@ program
|
|
|
98
99
|
const config = createInitConfig(configPath, options.force);
|
|
99
100
|
const successMessage = getInitSuccessMessage(existsBeforeInit, options.force);
|
|
100
101
|
fs.writeFileSync(configPath, JSON.stringify(config, null, 2), 'utf-8');
|
|
101
|
-
|
|
102
|
-
|
|
102
|
+
utils_1.logger.success(`${successMessage} ${configPath}`);
|
|
103
|
+
utils_1.logger.info('请根据需要修改配置文件,然后运行 swagger2api-v3 generate');
|
|
103
104
|
}
|
|
104
105
|
catch (error) {
|
|
105
|
-
|
|
106
|
+
utils_1.logger.error('创建配置文件失败', error);
|
|
106
107
|
process.exit(1);
|
|
107
108
|
}
|
|
108
109
|
});
|
|
@@ -115,29 +116,28 @@ program
|
|
|
115
116
|
const configPath = path.resolve(process.cwd(), options.config);
|
|
116
117
|
try {
|
|
117
118
|
if (!fs.existsSync(configPath)) {
|
|
118
|
-
|
|
119
|
+
utils_1.logger.error(`找不到配置文件: ${configPath}`);
|
|
119
120
|
process.exit(1);
|
|
120
121
|
}
|
|
121
122
|
const configContent = fs.readFileSync(configPath, 'utf-8');
|
|
122
123
|
const config = JSON.parse(configContent);
|
|
123
|
-
const { validateSwaggerConfig } = await
|
|
124
|
+
const { validateSwaggerConfig } = await import('../config/validator.js');
|
|
124
125
|
const errors = validateSwaggerConfig(config);
|
|
125
126
|
if (errors.length === 0) {
|
|
126
|
-
|
|
127
|
+
utils_1.logger.success('配置文件验证通过');
|
|
127
128
|
}
|
|
128
129
|
else {
|
|
129
|
-
|
|
130
|
-
errors.forEach((error) => console.error(` - ${error}`));
|
|
130
|
+
utils_1.logger.errorList('配置验证失败:', errors);
|
|
131
131
|
process.exit(1);
|
|
132
132
|
}
|
|
133
133
|
}
|
|
134
134
|
catch (error) {
|
|
135
135
|
if (error instanceof SyntaxError) {
|
|
136
|
-
|
|
137
|
-
|
|
136
|
+
utils_1.logger.error(`配置文件 JSON 格式错误: ${configPath}`);
|
|
137
|
+
utils_1.logger.error(`错误详情: ${error.message}`);
|
|
138
138
|
}
|
|
139
139
|
else {
|
|
140
|
-
|
|
140
|
+
utils_1.logger.error('配置文件验证失败', error);
|
|
141
141
|
}
|
|
142
142
|
process.exit(1);
|
|
143
143
|
}
|
|
@@ -175,6 +175,7 @@ function createDefaultConfig() {
|
|
|
175
175
|
generator: 'typescript',
|
|
176
176
|
requestStyle: 'generic',
|
|
177
177
|
groupByTags: true,
|
|
178
|
+
multiTagStrategy: 'first',
|
|
178
179
|
overwrite: true,
|
|
179
180
|
prefix: '',
|
|
180
181
|
lint: 'prettier --write',
|
|
@@ -202,12 +203,12 @@ function createDefaultConfig() {
|
|
|
202
203
|
*/
|
|
203
204
|
function getInitSuccessMessage(existsBeforeInit, force) {
|
|
204
205
|
if (existsBeforeInit && force) {
|
|
205
|
-
return '
|
|
206
|
+
return '配置文件已覆盖:';
|
|
206
207
|
}
|
|
207
208
|
if (existsBeforeInit) {
|
|
208
|
-
return '
|
|
209
|
+
return '配置文件已补全:';
|
|
209
210
|
}
|
|
210
|
-
return '
|
|
211
|
+
return '配置文件已创建:';
|
|
211
212
|
}
|
|
212
213
|
/**
|
|
213
214
|
* 读取已存在的配置文件
|
|
@@ -221,11 +222,11 @@ function readExistingConfig(configPath) {
|
|
|
221
222
|
}
|
|
222
223
|
catch (error) {
|
|
223
224
|
if (error instanceof SyntaxError) {
|
|
224
|
-
|
|
225
|
-
|
|
225
|
+
utils_1.logger.error(`配置文件 JSON 格式错误: ${configPath}`);
|
|
226
|
+
utils_1.logger.error(`错误详情: ${error.message}`);
|
|
226
227
|
}
|
|
227
228
|
else {
|
|
228
|
-
|
|
229
|
+
utils_1.logger.error('读取配置文件失败', error);
|
|
229
230
|
}
|
|
230
231
|
process.exit(1);
|
|
231
232
|
throw error;
|
package/dist/config/validator.js
CHANGED
|
@@ -7,6 +7,7 @@ const CONFIG_KEYS = [
|
|
|
7
7
|
'output',
|
|
8
8
|
'generator',
|
|
9
9
|
'groupByTags',
|
|
10
|
+
'multiTagStrategy',
|
|
10
11
|
'overwrite',
|
|
11
12
|
'prefix',
|
|
12
13
|
'requestStyle',
|
|
@@ -46,6 +47,7 @@ function validateSwaggerConfig(config) {
|
|
|
46
47
|
validateRequiredString(config.output, 'output', errors);
|
|
47
48
|
validateEnum(config.generator, 'generator', ['typescript', 'javascript'], errors);
|
|
48
49
|
validateBoolean(config.groupByTags, 'groupByTags', errors);
|
|
50
|
+
validateOptionalEnum(config.multiTagStrategy, 'multiTagStrategy', ['first', 'all'], errors);
|
|
49
51
|
validateOptionalBoolean(config.overwrite, 'overwrite', errors);
|
|
50
52
|
validateOptionalString(config.prefix, 'prefix', errors);
|
|
51
53
|
validateOptionalString(config.importTemplate, 'importTemplate', errors);
|
package/dist/core/generator.d.ts
CHANGED
|
@@ -61,6 +61,12 @@ export declare class CodeGenerator {
|
|
|
61
61
|
* @returns 类型字符串
|
|
62
62
|
*/
|
|
63
63
|
private getTypeFromSchema;
|
|
64
|
+
/**
|
|
65
|
+
* 获取当前配置下可安全输出的类型
|
|
66
|
+
* @param type 类型字符串
|
|
67
|
+
* @returns 安全类型字符串
|
|
68
|
+
*/
|
|
69
|
+
private getSafeGeneratedType;
|
|
64
70
|
/**
|
|
65
71
|
* 收集API数组中实际使用的类型
|
|
66
72
|
* @param apis API接口数组
|
|
@@ -93,11 +99,23 @@ export declare class CodeGenerator {
|
|
|
93
99
|
* @returns 请求配置代码
|
|
94
100
|
*/
|
|
95
101
|
private generateRequestConfig;
|
|
102
|
+
/**
|
|
103
|
+
* 生成 params 对象字段访问表达式
|
|
104
|
+
* @param name 参数名
|
|
105
|
+
* @returns 字段访问表达式
|
|
106
|
+
*/
|
|
107
|
+
private getParamAccessExpression;
|
|
96
108
|
/**
|
|
97
109
|
* 生成入口文件
|
|
98
110
|
* @param groupedApis 按标签分组的API
|
|
99
111
|
*/
|
|
100
112
|
private generateIndexFile;
|
|
113
|
+
/**
|
|
114
|
+
* 写入生成文件,overwrite=false 时保留已存在文件
|
|
115
|
+
* @param filePath 文件路径
|
|
116
|
+
* @param content 文件内容
|
|
117
|
+
*/
|
|
118
|
+
private writeGeneratedFile;
|
|
101
119
|
/**
|
|
102
120
|
* 获取标签对应的文件名
|
|
103
121
|
* @param tag 标签名
|
package/dist/core/generator.js
CHANGED
|
@@ -35,7 +35,11 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
36
|
exports.CodeGenerator = void 0;
|
|
37
37
|
const path = __importStar(require("path"));
|
|
38
|
+
const fs = __importStar(require("fs"));
|
|
39
|
+
const child_process_1 = require("child_process");
|
|
40
|
+
const util_1 = require("util");
|
|
38
41
|
const utils_1 = require("../utils");
|
|
42
|
+
const execPromise = (0, util_1.promisify)(child_process_1.exec);
|
|
39
43
|
/**
|
|
40
44
|
* 代码生成器
|
|
41
45
|
*/
|
|
@@ -87,7 +91,7 @@ class CodeGenerator {
|
|
|
87
91
|
async generateTypesFile(types) {
|
|
88
92
|
const content = this.generateTypesContent(types);
|
|
89
93
|
const filePath = path.join(this.config.output, 'types.ts');
|
|
90
|
-
|
|
94
|
+
this.writeGeneratedFile(filePath, content);
|
|
91
95
|
}
|
|
92
96
|
/**
|
|
93
97
|
* 生成类型文件内容
|
|
@@ -133,7 +137,7 @@ class CodeGenerator {
|
|
|
133
137
|
const ext = this.config.generator === 'javascript' ? 'js' : 'ts';
|
|
134
138
|
const filePath = path.join(tagFolderPath, `index.${ext}`);
|
|
135
139
|
const content = this.generateApiFileContent(apis, types, tag);
|
|
136
|
-
|
|
140
|
+
this.writeGeneratedFile(filePath, content);
|
|
137
141
|
}
|
|
138
142
|
}
|
|
139
143
|
/**
|
|
@@ -145,7 +149,7 @@ class CodeGenerator {
|
|
|
145
149
|
const ext = this.config.generator === 'javascript' ? 'js' : 'ts';
|
|
146
150
|
const filePath = path.join(this.config.output, `api.${ext}`);
|
|
147
151
|
const content = this.generateApiFileContent(apis, types);
|
|
148
|
-
|
|
152
|
+
this.writeGeneratedFile(filePath, content);
|
|
149
153
|
}
|
|
150
154
|
/**
|
|
151
155
|
* 生成API文件内容
|
|
@@ -197,7 +201,7 @@ class CodeGenerator {
|
|
|
197
201
|
type: p.type,
|
|
198
202
|
description: p.description
|
|
199
203
|
}));
|
|
200
|
-
const comment = (0, utils_1.generateApiComment)({ summary: api.description, deprecated:
|
|
204
|
+
const comment = (0, utils_1.generateApiComment)({ summary: api.description, deprecated: api.deprecated }, swaggerParams);
|
|
201
205
|
parts.push(comment);
|
|
202
206
|
}
|
|
203
207
|
// 生成函数签名
|
|
@@ -210,7 +214,9 @@ class CodeGenerator {
|
|
|
210
214
|
}));
|
|
211
215
|
// 生成直接参数形式
|
|
212
216
|
const functionParams = this.generateDirectParameters(swaggerParameters, this.config.generator === 'javascript');
|
|
213
|
-
const responseType =
|
|
217
|
+
const responseType = this.config.options?.generateModels === false
|
|
218
|
+
? 'any'
|
|
219
|
+
: api.responseType || 'any';
|
|
214
220
|
const functionName = (0, utils_1.toCamelCase)(api.name);
|
|
215
221
|
parts.push(`export const ${functionName} = (${functionParams}) => {`);
|
|
216
222
|
// 生成请求配置
|
|
@@ -251,7 +257,8 @@ class CodeGenerator {
|
|
|
251
257
|
const paramType = allParams
|
|
252
258
|
.map((p) => {
|
|
253
259
|
const optional = p.required ? '' : '?';
|
|
254
|
-
|
|
260
|
+
const type = this.getSafeGeneratedType(p.type);
|
|
261
|
+
return `${(0, utils_1.formatTsPropertyName)(p.name)}${optional}: ${type}`;
|
|
255
262
|
})
|
|
256
263
|
.join(', ');
|
|
257
264
|
// 检查是否所有参数都是可选的
|
|
@@ -270,7 +277,7 @@ class CodeGenerator {
|
|
|
270
277
|
const bodyType = bodyParam.schema
|
|
271
278
|
? this.getTypeFromSchema(bodyParam.schema)
|
|
272
279
|
: bodyParam.type;
|
|
273
|
-
params.push(`data: ${bodyType}`);
|
|
280
|
+
params.push(`data: ${this.getSafeGeneratedType(bodyType)}`);
|
|
274
281
|
}
|
|
275
282
|
}
|
|
276
283
|
// 添加可选的config参数
|
|
@@ -283,8 +290,23 @@ class CodeGenerator {
|
|
|
283
290
|
* @returns 类型字符串
|
|
284
291
|
*/
|
|
285
292
|
getTypeFromSchema(schema) {
|
|
293
|
+
if (this.config.options?.generateModels === false) {
|
|
294
|
+
return 'any';
|
|
295
|
+
}
|
|
286
296
|
return (0, utils_1.swaggerTypeToTsType)(schema);
|
|
287
297
|
}
|
|
298
|
+
/**
|
|
299
|
+
* 获取当前配置下可安全输出的类型
|
|
300
|
+
* @param type 类型字符串
|
|
301
|
+
* @returns 安全类型字符串
|
|
302
|
+
*/
|
|
303
|
+
getSafeGeneratedType(type) {
|
|
304
|
+
if (!type)
|
|
305
|
+
return 'any';
|
|
306
|
+
if (this.config.options?.generateModels !== false)
|
|
307
|
+
return type;
|
|
308
|
+
return this.isPrimitiveType(type) ? type : 'any';
|
|
309
|
+
}
|
|
288
310
|
/**
|
|
289
311
|
* 收集API数组中实际使用的类型
|
|
290
312
|
* @param apis API接口数组
|
|
@@ -406,8 +428,14 @@ class CodeGenerator {
|
|
|
406
428
|
}
|
|
407
429
|
// 查询参数和路径参数都从params对象中获取
|
|
408
430
|
if (queryParams.length > 0) {
|
|
409
|
-
|
|
410
|
-
|
|
431
|
+
if (pathParams.length > 0) {
|
|
432
|
+
const queryEntries = queryParams.map((param) => `${JSON.stringify(param.name)}: ${this.getParamAccessExpression(param.name)}`);
|
|
433
|
+
config.push(`params: { ${queryEntries.join(', ')} }`);
|
|
434
|
+
}
|
|
435
|
+
else {
|
|
436
|
+
// 直接传递params对象,让axios自动过滤undefined值
|
|
437
|
+
config.push('params');
|
|
438
|
+
}
|
|
411
439
|
}
|
|
412
440
|
// 请求体数据
|
|
413
441
|
const bodyParams = api.parameters.filter((p) => p.in === 'body');
|
|
@@ -422,6 +450,16 @@ class CodeGenerator {
|
|
|
422
450
|
config.push('...config');
|
|
423
451
|
return `{\n ${config.join(',\n ')}\n }`;
|
|
424
452
|
}
|
|
453
|
+
/**
|
|
454
|
+
* 生成 params 对象字段访问表达式
|
|
455
|
+
* @param name 参数名
|
|
456
|
+
* @returns 字段访问表达式
|
|
457
|
+
*/
|
|
458
|
+
getParamAccessExpression(name) {
|
|
459
|
+
return (0, utils_1.isValidIdentifier)(name)
|
|
460
|
+
? `params.${name}`
|
|
461
|
+
: `params[${JSON.stringify(name)}]`;
|
|
462
|
+
}
|
|
425
463
|
/**
|
|
426
464
|
* 生成入口文件
|
|
427
465
|
* @param groupedApis 按标签分组的API
|
|
@@ -433,20 +471,33 @@ class CodeGenerator {
|
|
|
433
471
|
this.config.options?.generateModels !== false) {
|
|
434
472
|
exports.push("export * from './types';");
|
|
435
473
|
}
|
|
436
|
-
if (this.config.
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
const
|
|
440
|
-
|
|
474
|
+
if (this.config.options?.generateApis !== false) {
|
|
475
|
+
if (this.config.groupByTags) {
|
|
476
|
+
// 按标签导出
|
|
477
|
+
for (const tag of groupedApis.keys()) {
|
|
478
|
+
const folderName = this.getTagFileName(tag);
|
|
479
|
+
exports.push(`export * from './${folderName}';`);
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
else {
|
|
483
|
+
// 导出单个API文件
|
|
484
|
+
exports.push("export * from './api';");
|
|
441
485
|
}
|
|
442
|
-
}
|
|
443
|
-
else {
|
|
444
|
-
// 导出单个API文件
|
|
445
|
-
exports.push("export * from './api';");
|
|
446
486
|
}
|
|
447
487
|
const content = [this.generateHeader(), '', ...exports, ''].join('\n');
|
|
448
488
|
const ext = this.config.generator === 'javascript' ? 'js' : 'ts';
|
|
449
489
|
const filePath = path.join(this.config.output, `index.${ext}`);
|
|
490
|
+
this.writeGeneratedFile(filePath, content);
|
|
491
|
+
}
|
|
492
|
+
/**
|
|
493
|
+
* 写入生成文件,overwrite=false 时保留已存在文件
|
|
494
|
+
* @param filePath 文件路径
|
|
495
|
+
* @param content 文件内容
|
|
496
|
+
*/
|
|
497
|
+
writeGeneratedFile(filePath, content) {
|
|
498
|
+
if (this.config.overwrite === false && fs.existsSync(filePath)) {
|
|
499
|
+
return;
|
|
500
|
+
}
|
|
450
501
|
(0, utils_1.writeFile)(filePath, content);
|
|
451
502
|
}
|
|
452
503
|
/**
|
|
@@ -487,18 +538,15 @@ class CodeGenerator {
|
|
|
487
538
|
if (!this.config.lint)
|
|
488
539
|
return;
|
|
489
540
|
try {
|
|
490
|
-
|
|
491
|
-
const util = require('util');
|
|
492
|
-
const execPromise = util.promisify(exec);
|
|
493
|
-
console.log(`🎨 Running lint command: ${this.config.lint} ${this.config.output}`);
|
|
541
|
+
utils_1.logger.info(`执行 Lint 命令: ${this.config.lint} ${this.config.output}`);
|
|
494
542
|
const result = await execPromise(`${this.config.lint} ${this.config.output}`);
|
|
495
543
|
if (result.stdout) {
|
|
496
|
-
|
|
544
|
+
utils_1.logger.info(result.stdout.trim());
|
|
497
545
|
}
|
|
498
|
-
|
|
546
|
+
utils_1.logger.success('Lint 执行完成');
|
|
499
547
|
}
|
|
500
548
|
catch (error) {
|
|
501
|
-
|
|
549
|
+
utils_1.logger.warn(`Lint 命令执行失败: ${error.message}`);
|
|
502
550
|
}
|
|
503
551
|
}
|
|
504
552
|
}
|
package/dist/core/parser.d.ts
CHANGED
|
@@ -37,12 +37,26 @@ export declare class SwaggerParser {
|
|
|
37
37
|
* @returns 类型信息
|
|
38
38
|
*/
|
|
39
39
|
private parseTypeDefinition;
|
|
40
|
+
/**
|
|
41
|
+
* 获取枚举成员名称
|
|
42
|
+
* @param schema 枚举 schema
|
|
43
|
+
* @param value 枚举值
|
|
44
|
+
* @param index 枚举值索引
|
|
45
|
+
* @returns 枚举成员名称
|
|
46
|
+
*/
|
|
47
|
+
private getEnumMemberName;
|
|
40
48
|
/**
|
|
41
49
|
* 解析本地 $ref 引用对象
|
|
42
50
|
* @param value 可能带有 $ref 的对象
|
|
43
51
|
* @returns 引用解析后的对象
|
|
44
52
|
*/
|
|
45
53
|
private resolveReference;
|
|
54
|
+
/**
|
|
55
|
+
* 合并同名同位置参数,后出现的 operation 级参数覆盖 path 级参数
|
|
56
|
+
* @param parameters 参数数组
|
|
57
|
+
* @returns 合并后的参数数组
|
|
58
|
+
*/
|
|
59
|
+
private mergeParameters;
|
|
46
60
|
/**
|
|
47
61
|
* 按标签分组API
|
|
48
62
|
* @param apis API接口数组
|
package/dist/core/parser.js
CHANGED
|
@@ -2,6 +2,16 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.SwaggerParser = void 0;
|
|
4
4
|
const utils_1 = require("../utils");
|
|
5
|
+
const HTTP_METHODS = [
|
|
6
|
+
'get',
|
|
7
|
+
'post',
|
|
8
|
+
'put',
|
|
9
|
+
'delete',
|
|
10
|
+
'patch',
|
|
11
|
+
'head',
|
|
12
|
+
'options'
|
|
13
|
+
];
|
|
14
|
+
const MAX_REF_RESOLVE_DEPTH = 20;
|
|
5
15
|
/**
|
|
6
16
|
* Swagger文档解析器
|
|
7
17
|
*/
|
|
@@ -25,16 +35,7 @@ class SwaggerParser {
|
|
|
25
35
|
const apis = [];
|
|
26
36
|
const paths = this.document.paths;
|
|
27
37
|
for (const [path, pathItem] of Object.entries(paths)) {
|
|
28
|
-
const
|
|
29
|
-
'get',
|
|
30
|
-
'post',
|
|
31
|
-
'put',
|
|
32
|
-
'delete',
|
|
33
|
-
'patch',
|
|
34
|
-
'head',
|
|
35
|
-
'options'
|
|
36
|
-
];
|
|
37
|
-
for (const method of methods) {
|
|
38
|
+
for (const method of HTTP_METHODS) {
|
|
38
39
|
const operation = pathItem[method];
|
|
39
40
|
if (!operation)
|
|
40
41
|
continue;
|
|
@@ -54,10 +55,7 @@ class SwaggerParser {
|
|
|
54
55
|
*/
|
|
55
56
|
parseOperation(method, path, operation, globalParameters) {
|
|
56
57
|
// 合并全局参数和操作参数
|
|
57
|
-
const allParameters = [
|
|
58
|
-
...(globalParameters || []),
|
|
59
|
-
...(operation.parameters || [])
|
|
60
|
-
].map((parameter) => this.resolveReference(parameter));
|
|
58
|
+
const allParameters = this.mergeParameters([...(globalParameters || []), ...(operation.parameters || [])].map((parameter) => this.resolveReference(parameter)));
|
|
61
59
|
// 处理 OpenAPI 3.0 requestBody
|
|
62
60
|
if (operation.requestBody) {
|
|
63
61
|
const requestBody = this.resolveReference(operation.requestBody);
|
|
@@ -97,8 +95,8 @@ class SwaggerParser {
|
|
|
97
95
|
const responseType = (0, utils_1.getResponseType)(operation.responses, this.getAllSchemas());
|
|
98
96
|
// 获取请求体类型
|
|
99
97
|
const bodyParam = allParameters.find((p) => p.in === 'body');
|
|
100
|
-
const requestBodyType = bodyParam
|
|
101
|
-
? (0, utils_1.swaggerTypeToTsType)(bodyParam.schema)
|
|
98
|
+
const requestBodyType = bodyParam?.schema
|
|
99
|
+
? (0, utils_1.swaggerTypeToTsType)(bodyParam.schema, this.getAllSchemas())
|
|
102
100
|
: undefined;
|
|
103
101
|
// 解析参数信息
|
|
104
102
|
const parameters = allParameters.map((param) => {
|
|
@@ -118,6 +116,7 @@ class SwaggerParser {
|
|
|
118
116
|
path,
|
|
119
117
|
description: operation.summary || operation.description,
|
|
120
118
|
tags: operation.tags || [],
|
|
119
|
+
deprecated: operation.deprecated,
|
|
121
120
|
parameters,
|
|
122
121
|
responseType,
|
|
123
122
|
requestBodyType
|
|
@@ -130,12 +129,12 @@ class SwaggerParser {
|
|
|
130
129
|
parseTypes() {
|
|
131
130
|
const types = [];
|
|
132
131
|
// Debug log
|
|
133
|
-
|
|
134
|
-
|
|
132
|
+
utils_1.logger.debug('解析类型定义...');
|
|
133
|
+
utils_1.logger.debugKV('components', !!this.document.components);
|
|
135
134
|
if (this.document.components) {
|
|
136
|
-
|
|
135
|
+
utils_1.logger.debugKV('schemas', !!this.document.components.schemas);
|
|
137
136
|
if (this.document.components.schemas) {
|
|
138
|
-
|
|
137
|
+
utils_1.logger.debugKV('schema 列表', Object.keys(this.document.components.schemas).join(', '));
|
|
139
138
|
}
|
|
140
139
|
}
|
|
141
140
|
// 解析 OpenAPI 3.x components.schemas
|
|
@@ -170,7 +169,7 @@ class SwaggerParser {
|
|
|
170
169
|
const comment = value.description
|
|
171
170
|
? ` /** ${value.description} */`
|
|
172
171
|
: '';
|
|
173
|
-
return `${comment}\n ${key}${optional}: ${type};`;
|
|
172
|
+
return `${comment}\n ${(0, utils_1.formatTsPropertyName)(key)}${optional}: ${type};`;
|
|
174
173
|
})
|
|
175
174
|
.join('\n');
|
|
176
175
|
definition = `export interface ${typeName} {\n${properties}\n}`;
|
|
@@ -178,29 +177,15 @@ class SwaggerParser {
|
|
|
178
177
|
else if (schema.type === 'array') {
|
|
179
178
|
// 数组类型 - 生成指向 items 类型的别名(不带 [])
|
|
180
179
|
// 在引用时会自动添加 []
|
|
181
|
-
const itemType = (0, utils_1.swaggerTypeToTsType)(schema.items, allSchemas);
|
|
180
|
+
const itemType = (0, utils_1.swaggerTypeToTsType)(schema.items || {}, allSchemas);
|
|
182
181
|
definition = `export type ${typeName} = ${itemType};`;
|
|
183
182
|
}
|
|
184
183
|
else if (schema.enum) {
|
|
185
184
|
// 枚举类型
|
|
186
185
|
const enumValues = schema.enum
|
|
187
186
|
.map((value, index) => {
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
if ((schema['x-enum-varnames'] && schema['x-enum-varnames'][index]) ||
|
|
191
|
-
(schema['x-enumNames'] && schema['x-enumNames'][index])) {
|
|
192
|
-
key =
|
|
193
|
-
schema['x-enum-varnames']?.[index] ||
|
|
194
|
-
schema['x-enumNames']?.[index];
|
|
195
|
-
}
|
|
196
|
-
else if (/^\d+$/.test(value)) {
|
|
197
|
-
// 对于数字枚举,使用 VALUE_ 前缀
|
|
198
|
-
key = `VALUE_${value}`;
|
|
199
|
-
}
|
|
200
|
-
else {
|
|
201
|
-
key = value.toUpperCase();
|
|
202
|
-
}
|
|
203
|
-
return ` ${key} = '${value}'`;
|
|
187
|
+
const key = this.getEnumMemberName(schema, value, index);
|
|
188
|
+
return ` ${key} = ${JSON.stringify(String(value))}`;
|
|
204
189
|
})
|
|
205
190
|
.join(',\n');
|
|
206
191
|
definition = `export enum ${typeName} {\n${enumValues}\n}`;
|
|
@@ -216,24 +201,68 @@ class SwaggerParser {
|
|
|
216
201
|
description: schema.description
|
|
217
202
|
};
|
|
218
203
|
}
|
|
204
|
+
/**
|
|
205
|
+
* 获取枚举成员名称
|
|
206
|
+
* @param schema 枚举 schema
|
|
207
|
+
* @param value 枚举值
|
|
208
|
+
* @param index 枚举值索引
|
|
209
|
+
* @returns 枚举成员名称
|
|
210
|
+
*/
|
|
211
|
+
getEnumMemberName(schema, value, index) {
|
|
212
|
+
const extensionName = schema['x-enum-varnames']?.[index] || schema['x-enumNames']?.[index];
|
|
213
|
+
if (extensionName) {
|
|
214
|
+
return extensionName;
|
|
215
|
+
}
|
|
216
|
+
const strValue = String(value);
|
|
217
|
+
if (/^\d+$/.test(strValue)) {
|
|
218
|
+
return `VALUE_${strValue}`;
|
|
219
|
+
}
|
|
220
|
+
return strValue.toUpperCase();
|
|
221
|
+
}
|
|
219
222
|
/**
|
|
220
223
|
* 解析本地 $ref 引用对象
|
|
221
224
|
* @param value 可能带有 $ref 的对象
|
|
222
225
|
* @returns 引用解析后的对象
|
|
223
226
|
*/
|
|
224
|
-
resolveReference(value) {
|
|
227
|
+
resolveReference(value, seenRefs = new Set(), depth = 0) {
|
|
225
228
|
if (!value || !value.$ref) {
|
|
226
229
|
return value;
|
|
227
230
|
}
|
|
228
|
-
const
|
|
231
|
+
const ref = value.$ref;
|
|
232
|
+
if (!ref.startsWith('#/')) {
|
|
233
|
+
throw new Error(`暂不支持外部 $ref 引用: ${ref}`);
|
|
234
|
+
}
|
|
235
|
+
if (depth >= MAX_REF_RESOLVE_DEPTH) {
|
|
236
|
+
throw new Error(`$ref 解析超过最大深度: ${ref}`);
|
|
237
|
+
}
|
|
238
|
+
if (seenRefs.has(ref)) {
|
|
239
|
+
throw new Error(`检测到循环 $ref 引用: ${ref}`);
|
|
240
|
+
}
|
|
241
|
+
seenRefs.add(ref);
|
|
242
|
+
const refPath = ref
|
|
243
|
+
.replace(/^#\//, '')
|
|
244
|
+
.split('/')
|
|
245
|
+
.map((segment) => decodeURIComponent(segment).replace(/~1/g, '/').replace(/~0/g, '~'));
|
|
229
246
|
let current = this.document;
|
|
230
247
|
for (const segment of refPath) {
|
|
231
248
|
current = current?.[segment];
|
|
232
|
-
if (
|
|
233
|
-
|
|
249
|
+
if (current === undefined) {
|
|
250
|
+
throw new Error(`无法解析 $ref 引用: ${ref}`);
|
|
234
251
|
}
|
|
235
252
|
}
|
|
236
|
-
return current;
|
|
253
|
+
return this.resolveReference(current, seenRefs, depth + 1);
|
|
254
|
+
}
|
|
255
|
+
/**
|
|
256
|
+
* 合并同名同位置参数,后出现的 operation 级参数覆盖 path 级参数
|
|
257
|
+
* @param parameters 参数数组
|
|
258
|
+
* @returns 合并后的参数数组
|
|
259
|
+
*/
|
|
260
|
+
mergeParameters(parameters) {
|
|
261
|
+
const merged = new Map();
|
|
262
|
+
for (const parameter of parameters) {
|
|
263
|
+
merged.set(`${parameter.in}:${parameter.name}`, parameter);
|
|
264
|
+
}
|
|
265
|
+
return Array.from(merged.values());
|
|
237
266
|
}
|
|
238
267
|
/**
|
|
239
268
|
* 按标签分组API
|
|
@@ -244,7 +273,9 @@ class SwaggerParser {
|
|
|
244
273
|
const groupedApis = new Map();
|
|
245
274
|
for (const api of apis) {
|
|
246
275
|
const tags = api.tags.length > 0 ? api.tags : ['default'];
|
|
247
|
-
|
|
276
|
+
const strategy = this.config.multiTagStrategy ?? 'first';
|
|
277
|
+
const groupTags = strategy === 'all' ? [tags.join('-')] : [tags[0]];
|
|
278
|
+
for (const tag of groupTags) {
|
|
248
279
|
if (!groupedApis.has(tag)) {
|
|
249
280
|
groupedApis.set(tag, []);
|
|
250
281
|
}
|
|
@@ -265,16 +296,7 @@ class SwaggerParser {
|
|
|
265
296
|
}
|
|
266
297
|
// 从路径操作中获取
|
|
267
298
|
for (const pathItem of Object.values(this.document.paths)) {
|
|
268
|
-
const
|
|
269
|
-
'get',
|
|
270
|
-
'post',
|
|
271
|
-
'put',
|
|
272
|
-
'delete',
|
|
273
|
-
'patch',
|
|
274
|
-
'head',
|
|
275
|
-
'options'
|
|
276
|
-
];
|
|
277
|
-
for (const method of methods) {
|
|
299
|
+
for (const method of HTTP_METHODS) {
|
|
278
300
|
const operation = pathItem[method];
|
|
279
301
|
if (operation?.tags) {
|
|
280
302
|
operation.tags.forEach((tag) => tags.add(tag));
|