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.
package/README.md CHANGED
@@ -2,11 +2,11 @@
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.x / Swagger 3.0 documentation.
5
+ A powerful command-line tool for automatically generating TypeScript or JavaScript interface code from OpenAPI 3.0 documentation.
6
6
 
7
7
  ## ✨ Features
8
8
 
9
- - 🚀 **Fast Generation** - Quickly generate TypeScript interface code from OpenAPI/Swagger 3.0 JSON
9
+ - 🚀 **Fast Generation** - Quickly generate TypeScript interface code from OpenAPI 3.0 JSON or YAML
10
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
@@ -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 | - | OpenAPI/Swagger 3.0 JSON file path or URL |
79
+ | `input` | string | - | OpenAPI 3.0 JSON/YAML file path or URL; local and remote external `$ref` references are bundled automatically |
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 |
@@ -10,11 +10,11 @@
10
10
  },
11
11
  "input": {
12
12
  "type": "string",
13
- "description": "OpenAPI JSON 文件路径或 URL"
13
+ "description": "OpenAPI 3.0 JSON/YAML 文件路径或 URL"
14
14
  },
15
15
  "output": {
16
16
  "type": "string",
17
- "description": "生成代码输出目录"
17
+ "description": "生成代码输出目录,不能是当前工作目录或其父目录"
18
18
  },
19
19
  "generator": {
20
20
  "enum": ["typescript", "javascript"],
@@ -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,12 +38,13 @@ 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'));
44
45
  program
45
46
  .name('swagger2api-v3')
46
- .description('从 Swagger/OpenAPI 文档生成 TypeScript API 接口')
47
+ .description('从 OpenAPI 3.0 文档生成 TypeScript API 接口')
47
48
  .version(packageJson.version);
48
49
  // generate 命令
49
50
  program
@@ -51,7 +52,7 @@ program
51
52
  .alias('gen')
52
53
  .description('根据配置文件生成 API 接口')
53
54
  .option('-c, --config <path>', '配置文件路径', '.swagger.config.json')
54
- .option('-i, --input <path>', 'Swagger JSON 文件路径或 URL')
55
+ .option('-i, --input <path>', 'OpenAPI JSON/YAML 文件路径或 URL')
55
56
  .option('-o, --output <path>', '输出目录')
56
57
  .option('--no-types', '不生成类型文件')
57
58
  .option('--no-group', '不按标签分组')
@@ -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;
@@ -1,12 +1,48 @@
1
1
  "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
2
35
  Object.defineProperty(exports, "__esModule", { value: true });
3
36
  exports.validateSwaggerConfig = validateSwaggerConfig;
37
+ const path = __importStar(require("path"));
38
+ const file_1 = require("../utils/file");
4
39
  const CONFIG_KEYS = [
5
40
  '$schema',
6
41
  'input',
7
42
  'output',
8
43
  'generator',
9
44
  'groupByTags',
45
+ 'multiTagStrategy',
10
46
  'overwrite',
11
47
  'prefix',
12
48
  'requestStyle',
@@ -42,10 +78,15 @@ const COMMENT_KEYS = [
42
78
  */
43
79
  function validateSwaggerConfig(config) {
44
80
  const errors = [];
81
+ if (!isPlainObject(config)) {
82
+ return ['config 必须是对象'];
83
+ }
45
84
  validateRequiredString(config.input, 'input', errors);
46
85
  validateRequiredString(config.output, 'output', errors);
86
+ validateOutputPath(config.output, errors);
47
87
  validateEnum(config.generator, 'generator', ['typescript', 'javascript'], errors);
48
88
  validateBoolean(config.groupByTags, 'groupByTags', errors);
89
+ validateOptionalEnum(config.multiTagStrategy, 'multiTagStrategy', ['first', 'all'], errors);
49
90
  validateOptionalBoolean(config.overwrite, 'overwrite', errors);
50
91
  validateOptionalString(config.prefix, 'prefix', errors);
51
92
  validateOptionalString(config.importTemplate, 'importTemplate', errors);
@@ -69,10 +110,25 @@ function validateSwaggerConfig(config) {
69
110
  * @param errors 错误信息数组
70
111
  */
71
112
  function validateRequiredString(value, field, errors) {
72
- if (typeof value !== 'string' || !value) {
113
+ if (typeof value !== 'string' || !value.trim()) {
73
114
  errors.push(`${field} 配置项不能为空,且必须是字符串`);
74
115
  }
75
116
  }
117
+ /**
118
+ * 验证输出路径不会覆盖当前工作目录或其父目录
119
+ * @param value 输出路径
120
+ * @param errors 错误信息数组
121
+ */
122
+ function validateOutputPath(value, errors) {
123
+ if (typeof value !== 'string' || !value.trim())
124
+ return;
125
+ const currentDirectory = path.resolve(process.cwd());
126
+ const outputDirectory = path.resolve(currentDirectory, value);
127
+ if (outputDirectory === currentDirectory ||
128
+ (0, file_1.isPathInside)(outputDirectory, currentDirectory)) {
129
+ errors.push('output 不能是当前工作目录或其父目录');
130
+ }
131
+ }
76
132
  /**
77
133
  * 验证可选字符串
78
134
  * @param value 字段值
@@ -149,13 +205,17 @@ function validateStringArray(value, field, errors) {
149
205
  * @param errors 错误信息数组
150
206
  */
151
207
  function validateFilter(config, errors) {
152
- if (!config.filter)
208
+ if (!validateOptionalPlainObject(config.filter, 'filter', errors))
153
209
  return;
154
210
  validateKnownKeys(config.filter, ['include', 'exclude'], 'filter', errors);
155
- validateKnownKeys(config.filter.include, ['tags'], 'filter.include', errors);
156
- validateKnownKeys(config.filter.exclude, ['tags'], 'filter.exclude', errors);
157
- validateStringArray(config.filter.include?.tags, 'filter.include.tags', errors);
158
- validateStringArray(config.filter.exclude?.tags, 'filter.exclude.tags', errors);
211
+ if (validateOptionalPlainObject(config.filter.include, 'filter.include', errors)) {
212
+ validateKnownKeys(config.filter.include, ['tags'], 'filter.include', errors);
213
+ validateStringArray(config.filter.include.tags, 'filter.include.tags', errors);
214
+ }
215
+ if (validateOptionalPlainObject(config.filter.exclude, 'filter.exclude', errors)) {
216
+ validateKnownKeys(config.filter.exclude, ['tags'], 'filter.exclude', errors);
217
+ validateStringArray(config.filter.exclude.tags, 'filter.exclude.tags', errors);
218
+ }
159
219
  }
160
220
  /**
161
221
  * 验证生成选项配置
@@ -163,7 +223,7 @@ function validateFilter(config, errors) {
163
223
  * @param errors 错误信息数组
164
224
  */
165
225
  function validateOptions(config, errors) {
166
- if (!config.options)
226
+ if (!validateOptionalPlainObject(config.options, 'options', errors))
167
227
  return;
168
228
  validateKnownKeys(config.options, OPTIONS_KEYS, 'options', errors);
169
229
  validateOptionalBoolean(config.options.generateModels, 'options.generateModels', errors);
@@ -179,8 +239,9 @@ function validateOptions(config, errors) {
179
239
  * @param errors 错误信息数组
180
240
  */
181
241
  function validateTagGrouping(config, errors) {
182
- if (!config.tagGrouping)
242
+ if (!validateOptionalPlainObject(config.tagGrouping, 'tagGrouping', errors)) {
183
243
  return;
244
+ }
184
245
  validateKnownKeys(config.tagGrouping, TAG_GROUPING_KEYS, 'tagGrouping', errors);
185
246
  validateOptionalBoolean(config.tagGrouping.enabled, 'tagGrouping.enabled', errors);
186
247
  validateOptionalBoolean(config.tagGrouping.createSubDirectories, 'tagGrouping.createSubDirectories', errors);
@@ -192,7 +253,7 @@ function validateTagGrouping(config, errors) {
192
253
  * @param errors 错误信息数组
193
254
  */
194
255
  function validateComments(config, errors) {
195
- if (!config.comments)
256
+ if (!validateOptionalPlainObject(config.comments, 'comments', errors))
196
257
  return;
197
258
  validateKnownKeys(config.comments, COMMENT_KEYS, 'comments', errors);
198
259
  validateOptionalBoolean(config.comments.includeDescription, 'comments.includeDescription', errors);
@@ -216,3 +277,27 @@ function validateKnownKeys(value, knownKeys, path, errors) {
216
277
  }
217
278
  }
218
279
  }
280
+ /**
281
+ * 验证可选配置值是否为普通对象
282
+ * @param value 配置值
283
+ * @param field 字段路径
284
+ * @param errors 错误信息数组
285
+ * @returns 配置值是否为普通对象
286
+ */
287
+ function validateOptionalPlainObject(value, field, errors) {
288
+ if (value === undefined)
289
+ return false;
290
+ if (!isPlainObject(value)) {
291
+ errors.push(`${field} 必须是对象`);
292
+ return false;
293
+ }
294
+ return true;
295
+ }
296
+ /**
297
+ * 判断值是否为普通对象
298
+ * @param value 待判断的值
299
+ * @returns 是否为普通对象
300
+ */
301
+ function isPlainObject(value) {
302
+ return !!value && typeof value === 'object' && !Array.isArray(value);
303
+ }
@@ -4,6 +4,11 @@ import { SwaggerConfig, ApiInfo, TypeInfo } from '../types';
4
4
  */
5
5
  export declare class CodeGenerator {
6
6
  private config;
7
+ private readonly outputRoot;
8
+ /**
9
+ * 创建代码生成器
10
+ * @param config 生成配置
11
+ */
7
12
  constructor(config: SwaggerConfig);
8
13
  /**
9
14
  * 生成所有文件
@@ -23,6 +28,31 @@ export declare class CodeGenerator {
23
28
  * @returns 类型文件内容
24
29
  */
25
30
  private generateTypesContent;
31
+ /**
32
+ * 渲染类型定义列表
33
+ * @param types 类型定义数组
34
+ * @returns 类型定义内容
35
+ */
36
+ private renderTypeDefinitions;
37
+ /**
38
+ * 合并类型定义
39
+ * @param modelTypes 组件模型类型
40
+ * @param parameterTypes 查询参数类型
41
+ * @returns 合并后的类型定义
42
+ */
43
+ private mergeTypeDefinitions;
44
+ /**
45
+ * 判断是否应提取查询参数类型
46
+ * @returns 是否提取查询参数类型
47
+ */
48
+ private shouldExtractQueryParameterTypes;
49
+ /**
50
+ * 生成 tag 内的查询参数类型文件
51
+ * @param folderName tag 目录名
52
+ * @param apis 当前 tag 的 API 接口
53
+ * @param modelTypes 组件模型类型
54
+ */
55
+ private generateTagParameterTypesFile;
26
56
  /**
27
57
  * 按标签生成API文件
28
58
  * @param groupedApis 按标签分组的API
@@ -46,25 +76,35 @@ export declare class CodeGenerator {
46
76
  /**
47
77
  * 生成单个API函数
48
78
  * @param api API接口信息
79
+ * @param queryParameterTypeName 查询参数类型名
49
80
  * @returns API函数代码
50
81
  */
51
82
  private generateApiFunction;
52
83
  /**
53
84
  * 生成直接参数形式
54
85
  * @param parameters OpenAPI 参数数组
86
+ * @param isJavaScript 是否生成 JavaScript 参数
87
+ * @param queryParameterTypeName 查询参数类型名
55
88
  * @returns 函数参数字符串
56
89
  */
57
90
  private generateDirectParameters;
58
91
  /**
59
- * 从schema获取类型
60
- * @param schema Swagger schema
61
- * @returns 类型字符串
92
+ * 获取当前配置下可安全输出的类型
93
+ * @param type 类型字符串
94
+ * @returns 安全类型字符串
62
95
  */
63
- private getTypeFromSchema;
96
+ private getSafeGeneratedType;
97
+ /**
98
+ * 渲染内联查询参数类型成员
99
+ * @param parameters 查询参数数组
100
+ * @returns 内联类型成员
101
+ */
102
+ private renderInlineQueryParameterMembers;
64
103
  /**
65
104
  * 收集API数组中实际使用的类型
66
105
  * @param apis API接口数组
67
106
  * @param definedTypes 已定义的类型数组
107
+ * @param excludeQueryParameters 是否排除查询参数
68
108
  * @returns 使用的类型名称数组
69
109
  */
70
110
  private collectUsedTypes;
@@ -80,30 +120,62 @@ export declare class CodeGenerator {
80
120
  * @returns 是否为基础类型
81
121
  */
82
122
  private isPrimitiveType;
83
- /**
84
- * 检测是否为通用响应容器类型
85
- * @param type 类型信息
86
- * @param definition 类型定义
87
- * @returns 是否为通用响应容器类型
88
- */
89
- private isGenericResponseContainer;
90
123
  /**
91
124
  * 生成请求配置
92
125
  * @param api API接口信息
93
126
  * @returns 请求配置代码
94
127
  */
95
128
  private generateRequestConfig;
129
+ /**
130
+ * 创建不重复的路径参数变量名
131
+ * @param parameters OpenAPI 路径参数
132
+ * @returns 路径参数变量名
133
+ */
134
+ private createPathParameterNames;
96
135
  /**
97
136
  * 生成入口文件
98
137
  * @param groupedApis 按标签分组的API
99
138
  */
100
139
  private generateIndexFile;
140
+ /**
141
+ * 校验输出目录不会覆盖当前工作目录或其父目录
142
+ */
143
+ private assertSafeOutputDirectory;
144
+ /**
145
+ * 解析并校验输出目录内的文件路径
146
+ * @param segments 输出目录下的路径片段
147
+ * @returns 安全的绝对路径
148
+ */
149
+ private resolveOutputPath;
150
+ /**
151
+ * 写入生成文件,overwrite=false 时保留已存在文件
152
+ * @param filePath 文件路径
153
+ * @param content 文件内容
154
+ */
155
+ private writeGeneratedFile;
101
156
  /**
102
157
  * 获取标签对应的文件名
103
158
  * @param tag 标签名
104
159
  * @returns 文件名
105
160
  */
106
161
  private getTagFileName;
162
+ /**
163
+ * 校验所有标签生成的目录名称安全且不重复
164
+ * @param groupedApis 按标签分组的 API
165
+ */
166
+ private validateTagFolders;
167
+ /**
168
+ * 转义模板字符串中的特殊字符
169
+ * @param value 原始字符串
170
+ * @returns 可安全写入模板字符串的内容
171
+ */
172
+ private escapeTemplateLiteral;
173
+ /**
174
+ * 将字符串转换为安全的单引号字面量
175
+ * @param value 原始字符串
176
+ * @returns 单引号字符串字面量
177
+ */
178
+ private toSingleQuotedString;
107
179
  /**
108
180
  * 生成文件头部注释
109
181
  * @param defaultHeader 默认头部注释