swagger2api-v3 1.1.9 → 1.1.11

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.
@@ -35,16 +35,23 @@ 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"));
38
39
  const child_process_1 = require("child_process");
39
40
  const util_1 = require("util");
40
41
  const utils_1 = require("../utils");
42
+ const query_parameter_generator_1 = require("./query-parameter-generator");
41
43
  const execPromise = (0, util_1.promisify)(child_process_1.exec);
42
44
  /**
43
45
  * 代码生成器
44
46
  */
45
47
  class CodeGenerator {
48
+ /**
49
+ * 创建代码生成器
50
+ * @param config 生成配置
51
+ */
46
52
  constructor(config) {
47
53
  this.config = config;
54
+ this.outputRoot = path.resolve(config.output);
48
55
  }
49
56
  /**
50
57
  * 生成所有文件
@@ -53,17 +60,27 @@ class CodeGenerator {
53
60
  * @param groupedApis 按标签分组的API
54
61
  */
55
62
  async generateAll(apis, types, groupedApis) {
63
+ this.assertSafeOutputDirectory();
64
+ if (this.config.options?.generateApis !== false &&
65
+ this.config.groupByTags) {
66
+ this.validateTagFolders(groupedApis);
67
+ }
56
68
  // 根据overwrite配置决定是否清空目录
57
69
  if (this.config.overwrite !== false) {
58
70
  // 默认为true,清空输出目录
59
- (0, utils_1.removeDirectory)(this.config.output);
71
+ (0, utils_1.removeDirectory)(this.outputRoot);
60
72
  }
61
73
  // 确保输出目录存在
62
- (0, utils_1.ensureDirectoryExists)(this.config.output);
74
+ (0, utils_1.ensureDirectoryExists)(this.outputRoot);
63
75
  // 生成类型文件(仅在 TypeScript 模式下生成)
64
76
  if (this.config.generator === 'typescript' &&
65
77
  this.config.options?.generateModels !== false) {
66
- await this.generateTypesFile(types);
78
+ const rootTypes = this.config.groupByTags
79
+ ? types
80
+ : this.config.options?.generateApis === false
81
+ ? types
82
+ : this.mergeTypeDefinitions(types, (0, query_parameter_generator_1.generateQueryParameterTypes)(apis, types.map((type) => type.name)));
83
+ await this.generateTypesFile(rootTypes);
67
84
  }
68
85
  // 生成API文件
69
86
  if (this.config.options?.generateApis !== false) {
@@ -89,8 +106,8 @@ class CodeGenerator {
89
106
  */
90
107
  async generateTypesFile(types) {
91
108
  const content = this.generateTypesContent(types);
92
- const filePath = path.join(this.config.output, 'types.ts');
93
- (0, utils_1.writeFile)(filePath, content);
109
+ const filePath = this.resolveOutputPath('types.ts');
110
+ this.writeGeneratedFile(filePath, content);
94
111
  }
95
112
  /**
96
113
  * 生成类型文件内容
@@ -104,23 +121,66 @@ class CodeGenerator {
104
121
  ' * 此文件由 swagger2api-v3 自动生成,请勿手动修改',
105
122
  ' */'
106
123
  ]);
107
- const typeDefinitions = types
124
+ const typeDefinitions = this.renderTypeDefinitions(types);
125
+ return `${header}\n\n${typeDefinitions}\n`;
126
+ }
127
+ /**
128
+ * 渲染类型定义列表
129
+ * @param types 类型定义数组
130
+ * @returns 类型定义内容
131
+ */
132
+ renderTypeDefinitions(types) {
133
+ return types
108
134
  .map((type) => {
109
135
  const comment = type.description
110
136
  ? `/**\n * ${type.description}\n */\n`
111
137
  : '';
112
- // 通用处理:检测通用响应容器类型并转换为泛型接口
113
- let definition = type.definition;
114
- if (this.isGenericResponseContainer(type, definition)) {
115
- const typeName = type.name;
116
- definition = definition
117
- .replace(`export interface ${typeName} {`, `export interface ${typeName}<T = Record<string, any>> {`)
118
- .replace('data: Record<string, any>;', 'data: T;');
119
- }
120
- return `${comment}${definition}`;
138
+ return `${comment}${type.definition}`;
121
139
  })
122
140
  .join('\n\n');
123
- return `${header}\n\n${typeDefinitions}\n`;
141
+ }
142
+ /**
143
+ * 合并类型定义
144
+ * @param modelTypes 组件模型类型
145
+ * @param parameterTypes 查询参数类型
146
+ * @returns 合并后的类型定义
147
+ */
148
+ mergeTypeDefinitions(modelTypes, parameterTypes) {
149
+ return [...modelTypes, ...parameterTypes];
150
+ }
151
+ /**
152
+ * 判断是否应提取查询参数类型
153
+ * @returns 是否提取查询参数类型
154
+ */
155
+ shouldExtractQueryParameterTypes() {
156
+ return (this.config.generator === 'typescript' &&
157
+ this.config.options?.generateModels !== false);
158
+ }
159
+ /**
160
+ * 生成 tag 内的查询参数类型文件
161
+ * @param folderName tag 目录名
162
+ * @param apis 当前 tag 的 API 接口
163
+ * @param modelTypes 组件模型类型
164
+ */
165
+ generateTagParameterTypesFile(folderName, apis, modelTypes) {
166
+ const parameterTypes = (0, query_parameter_generator_1.generateQueryParameterTypes)(apis, modelTypes.map((type) => type.name));
167
+ if (parameterTypes.length === 0)
168
+ return;
169
+ const usedModelTypes = (0, query_parameter_generator_1.collectQueryParameterModelTypes)(apis, modelTypes);
170
+ const content = [
171
+ this.generateHeader([
172
+ '/**',
173
+ ' * API 查询参数类型',
174
+ ' * 此文件由 swagger2api-v3 自动生成,请勿手动修改',
175
+ ' */'
176
+ ])
177
+ ];
178
+ if (usedModelTypes.length > 0) {
179
+ content.push(`import type { ${usedModelTypes.join(', ')} } from '../../types';`);
180
+ }
181
+ content.push('', this.renderTypeDefinitions(parameterTypes), '');
182
+ const filePath = this.resolveOutputPath(folderName, 'types', 'index.ts');
183
+ this.writeGeneratedFile(filePath, content.join('\n'));
124
184
  }
125
185
  /**
126
186
  * 按标签生成API文件
@@ -130,13 +190,16 @@ class CodeGenerator {
130
190
  async generateApiFilesByTags(groupedApis, types) {
131
191
  for (const [tag, apis] of groupedApis) {
132
192
  const folderName = this.getTagFileName(tag);
133
- const tagFolderPath = path.join(this.config.output, folderName);
193
+ const tagFolderPath = this.resolveOutputPath(folderName);
134
194
  // 确保tag文件夹存在
135
195
  (0, utils_1.ensureDirectoryExists)(tagFolderPath);
196
+ if (this.shouldExtractQueryParameterTypes()) {
197
+ this.generateTagParameterTypesFile(folderName, apis, types);
198
+ }
136
199
  const ext = this.config.generator === 'javascript' ? 'js' : 'ts';
137
200
  const filePath = path.join(tagFolderPath, `index.${ext}`);
138
201
  const content = this.generateApiFileContent(apis, types, tag);
139
- (0, utils_1.writeFile)(filePath, content);
202
+ this.writeGeneratedFile(filePath, content);
140
203
  }
141
204
  }
142
205
  /**
@@ -146,9 +209,9 @@ class CodeGenerator {
146
209
  */
147
210
  async generateSingleApiFile(apis, types) {
148
211
  const ext = this.config.generator === 'javascript' ? 'js' : 'ts';
149
- const filePath = path.join(this.config.output, `api.${ext}`);
212
+ const filePath = this.resolveOutputPath(`api.${ext}`);
150
213
  const content = this.generateApiFileContent(apis, types);
151
- (0, utils_1.writeFile)(filePath, content);
214
+ this.writeGeneratedFile(filePath, content);
152
215
  }
153
216
  /**
154
217
  * 生成API文件内容
@@ -168,39 +231,56 @@ class CodeGenerator {
168
231
  ]),
169
232
  importTemplate + ';'
170
233
  ];
234
+ const extractQueryParameterTypes = this.shouldExtractQueryParameterTypes();
235
+ const queryParameterTypes = extractQueryParameterTypes
236
+ ? (0, query_parameter_generator_1.generateQueryParameterTypes)(apis, types.map((type) => type.name))
237
+ : [];
238
+ const queryParameterTypeNames = queryParameterTypes.map((type) => type.name);
171
239
  // 收集当前文件实际使用的类型
172
- const usedTypes = this.collectUsedTypes(apis, types); // 传入 types
240
+ const usedTypes = this.collectUsedTypes(apis, types, extractQueryParameterTypes);
241
+ const queryParameterTypeByApi = new Map(queryParameterTypes.map((type) => [type.api, type.name]));
173
242
  // 添加类型导入(仅在 TypeScript 且生成类型文件时)
174
243
  if (this.config.generator === 'typescript' &&
175
244
  this.config.options?.generateModels !== false &&
176
- usedTypes.length > 0) {
177
- const typeNames = usedTypes.join(', ');
245
+ (usedTypes.length > 0 || (!tag && queryParameterTypeNames.length > 0))) {
246
+ const typeNames = Array.from(new Set([...usedTypes, ...(tag ? [] : queryParameterTypeNames)]))
247
+ .sort()
248
+ .join(', ');
178
249
  const typesPath = tag ? '../types' : './types';
179
250
  header.push(`import type { ${typeNames} } from '${typesPath}';`);
180
251
  }
252
+ if (tag && queryParameterTypeNames.length > 0) {
253
+ const typeNames = queryParameterTypeNames.sort().join(', ');
254
+ header.push(`import type { ${typeNames} } from './types';`);
255
+ }
181
256
  header.push('');
182
257
  const apiImplementations = apis
183
- .map((api) => this.generateApiFunction(api))
258
+ .map((api) => this.generateApiFunction(api, extractQueryParameterTypes
259
+ ? queryParameterTypeByApi.get(api)
260
+ : undefined))
184
261
  .join('\n\n');
185
262
  return `${header.join('\n')}${apiImplementations}\n`;
186
263
  }
187
264
  /**
188
265
  * 生成单个API函数
189
266
  * @param api API接口信息
267
+ * @param queryParameterTypeName 查询参数类型名
190
268
  * @returns API函数代码
191
269
  */
192
- generateApiFunction(api) {
270
+ generateApiFunction(api, queryParameterTypeName) {
193
271
  const parts = [];
194
272
  // 生成注释
195
273
  if (this.config.options?.addComments !== false) {
274
+ const pathParameterNames = this.createPathParameterNames(api.parameters.filter((parameter) => parameter.in === 'path'));
275
+ let pathParameterIndex = 0;
196
276
  const swaggerParams = api.parameters.map((p) => ({
197
- name: p.name,
277
+ name: p.in === 'path' ? pathParameterNames[pathParameterIndex++] : p.name,
198
278
  in: p.in,
199
279
  required: p.required,
200
280
  type: p.type,
201
281
  description: p.description
202
282
  }));
203
- const comment = (0, utils_1.generateApiComment)({ summary: api.description, deprecated: false }, swaggerParams);
283
+ const comment = (0, utils_1.generateApiComment)({ summary: api.description, deprecated: api.deprecated }, swaggerParams);
204
284
  parts.push(comment);
205
285
  }
206
286
  // 生成函数签名
@@ -212,8 +292,10 @@ class CodeGenerator {
212
292
  schema: p.schema || { type: p.type }
213
293
  }));
214
294
  // 生成直接参数形式
215
- const functionParams = this.generateDirectParameters(swaggerParameters, this.config.generator === 'javascript');
216
- const responseType = api.responseType || 'any';
295
+ const functionParams = this.generateDirectParameters(swaggerParameters, this.config.generator === 'javascript', queryParameterTypeName);
296
+ const responseType = this.config.options?.generateModels === false
297
+ ? 'any'
298
+ : api.responseType || 'any';
217
299
  const functionName = (0, utils_1.toCamelCase)(api.name);
218
300
  parts.push(`export const ${functionName} = (${functionParams}) => {`);
219
301
  // 生成请求配置
@@ -237,30 +319,41 @@ class CodeGenerator {
237
319
  /**
238
320
  * 生成直接参数形式
239
321
  * @param parameters OpenAPI 参数数组
322
+ * @param isJavaScript 是否生成 JavaScript 参数
323
+ * @param queryParameterTypeName 查询参数类型名
240
324
  * @returns 函数参数字符串
241
325
  */
242
- generateDirectParameters(parameters, isJavaScript = false) {
326
+ generateDirectParameters(parameters, isJavaScript = false, queryParameterTypeName) {
243
327
  const params = [];
244
328
  const queryParams = parameters.filter((p) => p.in === 'query');
245
329
  const pathParams = parameters.filter((p) => p.in === 'path');
246
330
  const bodyParams = parameters.filter((p) => p.in === 'body');
247
- // 合并路径参数和查询参数为一个params对象
248
- const allParams = [...pathParams, ...queryParams];
249
- if (allParams.length > 0) {
331
+ const bodyParam = bodyParams[0];
332
+ const hasRequiredBody = bodyParam?.required === true;
333
+ const pathParameterNames = this.createPathParameterNames(pathParams);
334
+ pathParams.forEach((parameter, index) => {
335
+ const parameterName = pathParameterNames[index];
336
+ if (isJavaScript) {
337
+ params.push(parameterName);
338
+ }
339
+ else {
340
+ params.push(`${parameterName}: ${this.getSafeGeneratedType(parameter.type)}`);
341
+ }
342
+ });
343
+ if (queryParams.length > 0) {
250
344
  if (isJavaScript) {
251
345
  params.push('params');
252
346
  }
253
347
  else {
254
- const paramType = allParams
255
- .map((p) => {
256
- const optional = p.required ? '' : '?';
257
- return `${p.name}${optional}: ${p.type}`;
258
- })
259
- .join(', ');
260
- // 检查是否所有参数都是可选的
261
- const allOptional = allParams.every((p) => !p.required);
262
- const optionalModifier = allOptional ? '?' : '';
263
- params.push(`params${optionalModifier}: { ${paramType} }`);
348
+ const allOptional = queryParams.every((p) => !p.required);
349
+ const optionalModifier = allOptional && !hasRequiredBody ? '?' : '';
350
+ if (queryParameterTypeName) {
351
+ params.push(`params${optionalModifier}: ${queryParameterTypeName}`);
352
+ }
353
+ else {
354
+ const paramType = this.renderInlineQueryParameterMembers(queryParams);
355
+ params.push(`params${optionalModifier}: { ${paramType} }`);
356
+ }
264
357
  }
265
358
  }
266
359
  // 请求体参数
@@ -269,11 +362,9 @@ class CodeGenerator {
269
362
  params.push('data');
270
363
  }
271
364
  else {
272
- const bodyParam = bodyParams[0];
273
- const bodyType = bodyParam.schema
274
- ? this.getTypeFromSchema(bodyParam.schema)
275
- : bodyParam.type;
276
- params.push(`data: ${bodyType}`);
365
+ const bodyType = bodyParam.type;
366
+ const optionalModifier = bodyParam.required ? '' : '?';
367
+ params.push(`data${optionalModifier}: ${this.getSafeGeneratedType(bodyType)}`);
277
368
  }
278
369
  }
279
370
  // 添加可选的config参数
@@ -281,20 +372,41 @@ class CodeGenerator {
281
372
  return params.join(', ');
282
373
  }
283
374
  /**
284
- * 从schema获取类型
285
- * @param schema Swagger schema
286
- * @returns 类型字符串
375
+ * 获取当前配置下可安全输出的类型
376
+ * @param type 类型字符串
377
+ * @returns 安全类型字符串
287
378
  */
288
- getTypeFromSchema(schema) {
289
- return (0, utils_1.swaggerTypeToTsType)(schema);
379
+ getSafeGeneratedType(type) {
380
+ if (!type)
381
+ return 'any';
382
+ if (this.config.options?.generateModels !== false)
383
+ return type;
384
+ return this.isPrimitiveType(type) ? type : 'any';
385
+ }
386
+ /**
387
+ * 渲染内联查询参数类型成员
388
+ * @param parameters 查询参数数组
389
+ * @returns 内联类型成员
390
+ */
391
+ renderInlineQueryParameterMembers(parameters) {
392
+ return parameters
393
+ .map((parameter) => {
394
+ const optional = parameter.required ? '' : '?';
395
+ let type = this.getSafeGeneratedType(parameter.type);
396
+ if (optional)
397
+ type = (0, utils_1.stripNullFromUnion)(type);
398
+ return `${(0, utils_1.formatTsPropertyName)(parameter.name)}${optional}: ${type}`;
399
+ })
400
+ .join(', ');
290
401
  }
291
402
  /**
292
403
  * 收集API数组中实际使用的类型
293
404
  * @param apis API接口数组
294
405
  * @param definedTypes 已定义的类型数组
406
+ * @param excludeQueryParameters 是否排除查询参数
295
407
  * @returns 使用的类型名称数组
296
408
  */
297
- collectUsedTypes(apis, definedTypes) {
409
+ collectUsedTypes(apis, definedTypes, excludeQueryParameters = false) {
298
410
  const usedTypes = new Set();
299
411
  apis.forEach((api) => {
300
412
  // 收集响应类型
@@ -306,17 +418,15 @@ class CodeGenerator {
306
418
  }
307
419
  // 收集参数类型
308
420
  api.parameters.forEach((param) => {
309
- if (param.schema) {
310
- const type = this.getTypeFromSchema(param.schema);
311
- if (type && type !== 'any' && !this.isPrimitiveType(type)) {
312
- this.extractTypeNames(type).forEach((typeName) => {
421
+ if (excludeQueryParameters && param.in === 'query')
422
+ return;
423
+ if (param.type && param.type !== 'any') {
424
+ if (!this.isPrimitiveType(param.type)) {
425
+ this.extractTypeNames(param.type).forEach((typeName) => {
313
426
  usedTypes.add(typeName);
314
427
  });
315
428
  }
316
429
  }
317
- else if (param.type && !this.isPrimitiveType(param.type)) {
318
- usedTypes.add(param.type);
319
- }
320
430
  });
321
431
  });
322
432
  // 如果提供了 definedTypes,则只保留已定义的类型
@@ -359,29 +469,13 @@ class CodeGenerator {
359
469
  'object',
360
470
  'array',
361
471
  'any',
472
+ 'blob',
362
473
  'void',
363
474
  'null',
364
475
  'undefined'
365
476
  ];
366
477
  return primitiveTypes.includes(type.toLowerCase());
367
478
  }
368
- /**
369
- * 检测是否为通用响应容器类型
370
- * @param type 类型信息
371
- * @param definition 类型定义
372
- * @returns 是否为通用响应容器类型
373
- */
374
- isGenericResponseContainer(type, definition) {
375
- // 检查是否为接口定义
376
- if (!definition.includes(`export interface ${type.name} {`)) {
377
- return false;
378
- }
379
- // 检查是否包含 data 字段且类型为 Record<string, any>
380
- const hasDataField = definition.includes('data: Record<string, any>;');
381
- // 检查是否包含其他常见的响应容器字段
382
- const hasCommonFields = ['code', 'message', 'success', 'status'].some((field) => new RegExp(`\\b${field}\\??:`).test(definition));
383
- return hasDataField && hasCommonFields;
384
- }
385
479
  /**
386
480
  * 生成请求配置
387
481
  * @param api API接口信息
@@ -397,19 +491,20 @@ class CodeGenerator {
397
491
  }
398
492
  const pathParams = api.parameters.filter((p) => p.in === 'path');
399
493
  const queryParams = api.parameters.filter((p) => p.in === 'query');
494
+ const pathParameterNames = this.createPathParameterNames(pathParams);
400
495
  if (pathParams.length > 0) {
401
- // 替换路径参数,从params对象中获取
402
- pathParams.forEach((param) => {
403
- url = url.replace(`{${param.name}}`, `\${params.${param.name}}`);
496
+ url = this.escapeTemplateLiteral(url);
497
+ pathParams.forEach((param, index) => {
498
+ const placeholder = this.escapeTemplateLiteral(`{${param.name}}`);
499
+ const replacement = `\${${pathParameterNames[index]}}`;
500
+ url = url.split(placeholder).join(replacement);
404
501
  });
405
502
  config.push(`url: \`${url}\``);
406
503
  }
407
504
  else {
408
- config.push(`url: '${url}'`);
505
+ config.push(`url: ${this.toSingleQuotedString(url)}`);
409
506
  }
410
- // 查询参数和路径参数都从params对象中获取
411
507
  if (queryParams.length > 0) {
412
- // 直接传递params对象,让axios自动过滤undefined值
413
508
  config.push('params');
414
509
  }
415
510
  // 请求体数据
@@ -425,6 +520,28 @@ class CodeGenerator {
425
520
  config.push('...config');
426
521
  return `{\n ${config.join(',\n ')}\n }`;
427
522
  }
523
+ /**
524
+ * 创建不重复的路径参数变量名
525
+ * @param parameters OpenAPI 路径参数
526
+ * @returns 路径参数变量名
527
+ */
528
+ createPathParameterNames(parameters) {
529
+ const usedNames = new Set(['params', 'data', 'config']);
530
+ return parameters.map((parameter, index) => {
531
+ const normalizedName = parameter.name.replace(/[^A-Za-z0-9_$]/g, '_');
532
+ const baseName = (0, utils_1.isValidIdentifier)(normalizedName)
533
+ ? normalizedName
534
+ : `pathParam${index + 1}`;
535
+ let parameterName = baseName;
536
+ let suffix = 2;
537
+ while (usedNames.has(parameterName)) {
538
+ parameterName = `${baseName}_${suffix}`;
539
+ suffix++;
540
+ }
541
+ usedNames.add(parameterName);
542
+ return parameterName;
543
+ });
544
+ }
428
545
  /**
429
546
  * 生成入口文件
430
547
  * @param groupedApis 按标签分组的API
@@ -441,7 +558,7 @@ class CodeGenerator {
441
558
  // 按标签导出
442
559
  for (const tag of groupedApis.keys()) {
443
560
  const folderName = this.getTagFileName(tag);
444
- exports.push(`export * from './${folderName}';`);
561
+ exports.push(`export * from ${this.toSingleQuotedString(`./${folderName}`)};`);
445
562
  }
446
563
  }
447
564
  else {
@@ -451,7 +568,40 @@ class CodeGenerator {
451
568
  }
452
569
  const content = [this.generateHeader(), '', ...exports, ''].join('\n');
453
570
  const ext = this.config.generator === 'javascript' ? 'js' : 'ts';
454
- const filePath = path.join(this.config.output, `index.${ext}`);
571
+ const filePath = this.resolveOutputPath(`index.${ext}`);
572
+ this.writeGeneratedFile(filePath, content);
573
+ }
574
+ /**
575
+ * 校验输出目录不会覆盖当前工作目录或其父目录
576
+ */
577
+ assertSafeOutputDirectory() {
578
+ const currentDirectory = path.resolve(process.cwd());
579
+ if (this.outputRoot === currentDirectory ||
580
+ (0, utils_1.isPathInside)(this.outputRoot, currentDirectory)) {
581
+ throw new Error('输出目录不能是当前工作目录或其父目录');
582
+ }
583
+ }
584
+ /**
585
+ * 解析并校验输出目录内的文件路径
586
+ * @param segments 输出目录下的路径片段
587
+ * @returns 安全的绝对路径
588
+ */
589
+ resolveOutputPath(...segments) {
590
+ const targetPath = path.resolve(this.outputRoot, ...segments);
591
+ if (!(0, utils_1.isPathInside)(this.outputRoot, targetPath)) {
592
+ throw new Error(`生成路径超出输出目录: ${targetPath}`);
593
+ }
594
+ return targetPath;
595
+ }
596
+ /**
597
+ * 写入生成文件,overwrite=false 时保留已存在文件
598
+ * @param filePath 文件路径
599
+ * @param content 文件内容
600
+ */
601
+ writeGeneratedFile(filePath, content) {
602
+ if (this.config.overwrite === false && fs.existsSync(filePath)) {
603
+ return;
604
+ }
455
605
  (0, utils_1.writeFile)(filePath, content);
456
606
  }
457
607
  /**
@@ -461,16 +611,67 @@ class CodeGenerator {
461
611
  */
462
612
  getTagFileName(tag) {
463
613
  const cleanTag = (0, utils_1.sanitizeFilename)(tag);
614
+ let folderName;
464
615
  switch (this.config.tagGrouping?.fileNaming) {
465
616
  case 'camelCase':
466
- return (0, utils_1.toCamelCase)(cleanTag);
617
+ folderName = (0, utils_1.toCamelCase)(cleanTag);
618
+ break;
467
619
  case 'kebab-case':
468
- return (0, utils_1.toKebabCase)(cleanTag);
620
+ folderName = (0, utils_1.toKebabCase)(cleanTag);
621
+ break;
469
622
  case 'tag':
470
- return cleanTag.toLowerCase();
623
+ folderName = cleanTag.toLowerCase();
624
+ break;
471
625
  default:
472
- return (0, utils_1.toCamelCase)(cleanTag);
626
+ folderName = (0, utils_1.toCamelCase)(cleanTag);
627
+ break;
628
+ }
629
+ if (!folderName || folderName === '.' || folderName === '..') {
630
+ throw new Error(`无效的标签名称: ${JSON.stringify(tag)}`);
473
631
  }
632
+ return folderName;
633
+ }
634
+ /**
635
+ * 校验所有标签生成的目录名称安全且不重复
636
+ * @param groupedApis 按标签分组的 API
637
+ */
638
+ validateTagFolders(groupedApis) {
639
+ const folderOwners = new Map();
640
+ for (const tag of groupedApis.keys()) {
641
+ const folderName = this.getTagFileName(tag);
642
+ const normalizedFolderName = folderName.toLowerCase();
643
+ const existingTag = folderOwners.get(normalizedFolderName);
644
+ if (existingTag !== undefined && existingTag !== tag) {
645
+ throw new Error(`标签目录名称冲突: ${existingTag} 和 ${tag} 都会生成 ${folderName}`);
646
+ }
647
+ folderOwners.set(normalizedFolderName, tag);
648
+ }
649
+ }
650
+ /**
651
+ * 转义模板字符串中的特殊字符
652
+ * @param value 原始字符串
653
+ * @returns 可安全写入模板字符串的内容
654
+ */
655
+ escapeTemplateLiteral(value) {
656
+ return value
657
+ .replace(/\\/g, '\\\\')
658
+ .replace(/`/g, '\\`')
659
+ .replace(/\$\{/g, '\\${');
660
+ }
661
+ /**
662
+ * 将字符串转换为安全的单引号字面量
663
+ * @param value 原始字符串
664
+ * @returns 单引号字符串字面量
665
+ */
666
+ toSingleQuotedString(value) {
667
+ const escapedValue = value
668
+ .replace(/\\/g, '\\\\')
669
+ .replace(/'/g, "\\'")
670
+ .replace(/\r/g, '\\r')
671
+ .replace(/\n/g, '\\n')
672
+ .replace(/\u2028/g, '\\u2028')
673
+ .replace(/\u2029/g, '\\u2029');
674
+ return `'${escapedValue}'`;
474
675
  }
475
676
  /**
476
677
  * 生成文件头部注释
@@ -492,15 +693,15 @@ class CodeGenerator {
492
693
  if (!this.config.lint)
493
694
  return;
494
695
  try {
495
- console.log(`🎨 Running lint command: ${this.config.lint} ${this.config.output}`);
696
+ utils_1.logger.info(`执行 Lint 命令: ${this.config.lint} ${this.config.output}`);
496
697
  const result = await execPromise(`${this.config.lint} ${this.config.output}`);
497
698
  if (result.stdout) {
498
- console.log(result.stdout);
699
+ utils_1.logger.info(result.stdout.trim());
499
700
  }
500
- console.log('Lint completed successfully');
701
+ utils_1.logger.success('Lint 执行完成');
501
702
  }
502
703
  catch (error) {
503
- console.warn(`⚠️ Failed to run lint command:`, error.message);
704
+ utils_1.logger.warn(`Lint 命令执行失败: ${error.message}`);
504
705
  }
505
706
  }
506
707
  }
@@ -1,10 +1,15 @@
1
1
  import { SwaggerDocument, ApiInfo, TypeInfo, SwaggerConfig } from '../types';
2
2
  /**
3
- * Swagger文档解析器
3
+ * OpenAPI 3.0 文档解析器
4
4
  */
5
5
  export declare class SwaggerParser {
6
6
  private document;
7
7
  private config;
8
+ /**
9
+ * 创建 OpenAPI 文档解析器
10
+ * @param document OpenAPI 文档
11
+ * @param config 生成配置
12
+ */
8
13
  constructor(document: SwaggerDocument, config: SwaggerConfig);
9
14
  /**
10
15
  * 获取 OpenAPI components.schemas
@@ -30,27 +35,26 @@ export declare class SwaggerParser {
30
35
  * @returns 类型信息数组
31
36
  */
32
37
  parseTypes(): TypeInfo[];
33
- /**
34
- * 解析单个类型定义
35
- * @param name 类型名称
36
- * @param schema 模式对象
37
- * @returns 类型信息
38
- */
39
- private parseTypeDefinition;
40
- /**
41
- * 获取枚举成员名称
42
- * @param schema 枚举 schema
43
- * @param value 枚举值
44
- * @param index 枚举值索引
45
- * @returns 枚举成员名称
46
- */
47
- private getEnumMemberName;
48
38
  /**
49
39
  * 解析本地 $ref 引用对象
50
40
  * @param value 可能带有 $ref 的对象
41
+ * @param seenRefs 已访问的引用
42
+ * @param depth 当前解析深度
51
43
  * @returns 引用解析后的对象
52
44
  */
53
45
  private resolveReference;
46
+ /**
47
+ * 解析响应对象中的本地引用
48
+ * @param responses OpenAPI 响应集合
49
+ * @returns 已解析的响应集合
50
+ */
51
+ private resolveResponses;
52
+ /**
53
+ * 合并同名同位置参数,后出现的 operation 级参数覆盖 path 级参数
54
+ * @param parameters 参数数组
55
+ * @returns 合并后的参数数组
56
+ */
57
+ private mergeParameters;
54
58
  /**
55
59
  * 按标签分组API
56
60
  * @param apis API接口数组