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 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 Swagger tags
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 Promise.resolve().then(() => __importStar(require('../index')));
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
- console.error('❌ 生成失败:', error);
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
- console.log(successMessage, configPath);
102
- console.log('💡 请根据需要修改配置文件,然后运行 swagger2api-v3 generate');
102
+ utils_1.logger.success(`${successMessage} ${configPath}`);
103
+ utils_1.logger.info('请根据需要修改配置文件,然后运行 swagger2api-v3 generate');
103
104
  }
104
105
  catch (error) {
105
- console.error('❌ 创建配置文件失败:', error);
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
- console.error(`❌ 找不到配置文件: ${configPath}`);
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 Promise.resolve().then(() => __importStar(require('../config/validator')));
124
+ const { validateSwaggerConfig } = await import('../config/validator.js');
124
125
  const errors = validateSwaggerConfig(config);
125
126
  if (errors.length === 0) {
126
- console.log('配置文件验证通过');
127
+ utils_1.logger.success('配置文件验证通过');
127
128
  }
128
129
  else {
129
- console.error('配置验证失败:');
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
- console.error(`❌ 配置文件 JSON 格式错误: ${configPath}`);
137
- console.error('错误详情:', error.message);
136
+ utils_1.logger.error(`配置文件 JSON 格式错误: ${configPath}`);
137
+ utils_1.logger.error(`错误详情: ${error.message}`);
138
138
  }
139
139
  else {
140
- console.error('❌ 配置文件验证失败:', error);
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
- console.error(`❌ 配置文件 JSON 格式错误: ${configPath}`);
225
- console.error('错误详情:', error.message);
225
+ utils_1.logger.error(`配置文件 JSON 格式错误: ${configPath}`);
226
+ utils_1.logger.error(`错误详情: ${error.message}`);
226
227
  }
227
228
  else {
228
- console.error('❌ 读取配置文件失败:', error);
229
+ utils_1.logger.error('读取配置文件失败', error);
229
230
  }
230
231
  process.exit(1);
231
232
  throw error;
@@ -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);
@@ -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 标签名
@@ -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
- (0, utils_1.writeFile)(filePath, content);
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
- (0, utils_1.writeFile)(filePath, content);
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
- (0, utils_1.writeFile)(filePath, content);
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: false }, swaggerParams);
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 = api.responseType || 'any';
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
- return `${p.name}${optional}: ${p.type}`;
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
- // 直接传递params对象,让axios自动过滤undefined值
410
- config.push('params');
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.groupByTags) {
437
- // 按标签导出
438
- for (const tag of groupedApis.keys()) {
439
- const folderName = this.getTagFileName(tag);
440
- exports.push(`export * from './${folderName}';`);
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
- const { exec } = require('child_process');
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
- console.log(result.stdout);
544
+ utils_1.logger.info(result.stdout.trim());
497
545
  }
498
- console.log('Lint completed successfully');
546
+ utils_1.logger.success('Lint 执行完成');
499
547
  }
500
548
  catch (error) {
501
- console.warn(`⚠️ Failed to run lint command:`, error.message);
549
+ utils_1.logger.warn(`Lint 命令执行失败: ${error.message}`);
502
550
  }
503
551
  }
504
552
  }
@@ -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接口数组
@@ -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 methods = [
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
- console.log('解析类型定义...');
134
- console.log('Has components:', !!this.document.components);
132
+ utils_1.logger.debug('解析类型定义...');
133
+ utils_1.logger.debugKV('components', !!this.document.components);
135
134
  if (this.document.components) {
136
- console.log('Has schemas:', !!this.document.components.schemas);
135
+ utils_1.logger.debugKV('schemas', !!this.document.components.schemas);
137
136
  if (this.document.components.schemas) {
138
- console.log('Schema keys:', Object.keys(this.document.components.schemas));
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
- let key = value;
189
- // 优先使用 x-enum-varnames 或 x-enumNames 扩展字段
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 refPath = value.$ref.replace(/^#\//, '').split('/');
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 (!current) {
233
- return value;
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
- for (const tag of tags) {
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 methods = [
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));