jszy-swagger-doc-generator 1.1.0
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/OpenAPI.md +298 -0
- package/README.md +193 -0
- package/__tests__/index.test.ts +152 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +154 -0
- package/dist/cli.js.map +1 -0
- package/dist/index.d.ts +137 -0
- package/dist/index.js +489 -0
- package/dist/index.js.map +1 -0
- package/package.json +54 -0
- package/src/cli.ts +122 -0
- package/src/index.ts +584 -0
- package/tsconfig.json +23 -0
package/OpenAPI.md
ADDED
|
@@ -0,0 +1,298 @@
|
|
|
1
|
+
## 1. 后端 OpenAPI 文档要求
|
|
2
|
+
|
|
3
|
+
为了能够正确生成前端 SDK,后端需要提供符合特定格式的 OpenAPI 3.1 规范文档。以下是具体要求:
|
|
4
|
+
|
|
5
|
+
### 1.1 OpenAPI 文档结构要求
|
|
6
|
+
|
|
7
|
+
后端 API 文档必须遵循 OpenAPI 3.1 规范,并包含以下必要组成部分:
|
|
8
|
+
|
|
9
|
+
#### 1.1.1 文档根结构
|
|
10
|
+
```json
|
|
11
|
+
{
|
|
12
|
+
"openapi": "3.1.0-rc0",
|
|
13
|
+
"info": {
|
|
14
|
+
"title": "服务名称",
|
|
15
|
+
"description": "服务描述",
|
|
16
|
+
"version": "API 版本"
|
|
17
|
+
},
|
|
18
|
+
"servers": [
|
|
19
|
+
{
|
|
20
|
+
"url": "服务地址",
|
|
21
|
+
"description": "环境描述",
|
|
22
|
+
"x-environment": "环境标识"
|
|
23
|
+
}
|
|
24
|
+
],
|
|
25
|
+
"components": {
|
|
26
|
+
"schemas": {
|
|
27
|
+
// 数据模型定义
|
|
28
|
+
}
|
|
29
|
+
},
|
|
30
|
+
"paths": {
|
|
31
|
+
// API 端点定义
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
#### 1.1.2 数据模型定义 (components.schemas)
|
|
37
|
+
|
|
38
|
+
数据模型必须按照以下格式定义:
|
|
39
|
+
|
|
40
|
+
1. **对象类型**:
|
|
41
|
+
```json
|
|
42
|
+
"ModelName": {
|
|
43
|
+
"type": "object",
|
|
44
|
+
"properties": {
|
|
45
|
+
"field_name": {
|
|
46
|
+
"type": "数据类型", // string, integer, number, boolean, array 等
|
|
47
|
+
"format": "格式说明", // 如 timestamp, date 等
|
|
48
|
+
"pattern": "正则表达式", // 如 "^\\d{4}-\\d{2}-\\d{2}$"
|
|
49
|
+
"title": "字段标题" // 作为 TypeScript 注释
|
|
50
|
+
}
|
|
51
|
+
},
|
|
52
|
+
"required": ["必需字段列表"]
|
|
53
|
+
}
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
2. **枚举类型**:
|
|
57
|
+
```json
|
|
58
|
+
"EnumName": {
|
|
59
|
+
"type": "string",
|
|
60
|
+
"enum": ["值1", "值2", "值3"]
|
|
61
|
+
}
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
3. **联合类型 (oneOf)**:
|
|
65
|
+
```json
|
|
66
|
+
"UnionType": {
|
|
67
|
+
"oneOf": [
|
|
68
|
+
{
|
|
69
|
+
"type": "null"
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
"$ref": "#/components/schemas/OtherModel"
|
|
73
|
+
}
|
|
74
|
+
]
|
|
75
|
+
}
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
4. **数组类型**:
|
|
79
|
+
```json
|
|
80
|
+
"ArrayField": {
|
|
81
|
+
"type": "array",
|
|
82
|
+
"items": {
|
|
83
|
+
"$ref": "#/components/schemas/ItemType"
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
5. **可空类型处理**:
|
|
89
|
+
- 使用数组形式 `"type": ["string", "null"]`
|
|
90
|
+
- 或使用 `oneOf` 包含 null 类型
|
|
91
|
+
|
|
92
|
+
#### 1.1.3 API 端点定义 (paths)
|
|
93
|
+
|
|
94
|
+
API 端点必须按照以下格式定义:
|
|
95
|
+
|
|
96
|
+
1. **路径和操作**:
|
|
97
|
+
```json
|
|
98
|
+
"/api/path/{paramName}": {
|
|
99
|
+
"get|post|put|delete": {
|
|
100
|
+
"summary": "API 摘要", // 作为 TypeScript 注释
|
|
101
|
+
"description": "API 描述", // 作为 TypeScript 注释
|
|
102
|
+
"tags": ["分组名称"], // 用于 API 分组
|
|
103
|
+
"operationId": "apiMethodName", // 生成的 API 方法名
|
|
104
|
+
"parameters": [...], // 请求参数
|
|
105
|
+
"requestBody": {...}, // 请求体 (POST/PUT)
|
|
106
|
+
"responses": {...} // 响应定义
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
2. **参数定义**:
|
|
112
|
+
```json
|
|
113
|
+
"parameters": [
|
|
114
|
+
{
|
|
115
|
+
"name": "参数名",
|
|
116
|
+
"in": "path|query|header|cookie", // 参数位置
|
|
117
|
+
"schema": {
|
|
118
|
+
"type": "参数类型",
|
|
119
|
+
"format": "格式"
|
|
120
|
+
},
|
|
121
|
+
"required": true|false, // 是否必需
|
|
122
|
+
"description": "参数描述"
|
|
123
|
+
}
|
|
124
|
+
]
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
3. **请求体定义**:
|
|
128
|
+
```json
|
|
129
|
+
"requestBody": {
|
|
130
|
+
"description": "请求体描述",
|
|
131
|
+
"content": {
|
|
132
|
+
"application/json": {
|
|
133
|
+
"schema": {
|
|
134
|
+
"$ref": "#/components/schemas/RequestBodyModel"
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
},
|
|
138
|
+
"required": true
|
|
139
|
+
}
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
4. **响应定义**:
|
|
143
|
+
```json
|
|
144
|
+
"responses": {
|
|
145
|
+
"200": {
|
|
146
|
+
"description": "成功响应描述",
|
|
147
|
+
"content": {
|
|
148
|
+
"application/json": {
|
|
149
|
+
"schema": {
|
|
150
|
+
"$ref": "#/components/schemas/ResponseModel"
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
},
|
|
155
|
+
"201": {
|
|
156
|
+
// 创建成功响应
|
|
157
|
+
},
|
|
158
|
+
"204": {
|
|
159
|
+
"description": "No Content" // 无返回值
|
|
160
|
+
},
|
|
161
|
+
"500": {
|
|
162
|
+
// 错误响应
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
### 1.2 特殊类型处理
|
|
168
|
+
|
|
169
|
+
#### 1.2.1 引用处理
|
|
170
|
+
- 使用 `$ref` 引用定义在 `components.schemas` 中的模型
|
|
171
|
+
- 格式:`"$ref": "#/components/schemas/ModelName"`
|
|
172
|
+
|
|
173
|
+
#### 1.2.2 类型映射
|
|
174
|
+
- `integer` → TypeScript `number`
|
|
175
|
+
- `string` → TypeScript `string`
|
|
176
|
+
- `boolean` → TypeScript `boolean`
|
|
177
|
+
- `array` → TypeScript `Type[]`
|
|
178
|
+
- `null` → TypeScript 可空类型
|
|
179
|
+
|
|
180
|
+
#### 1.2.3 字段特性
|
|
181
|
+
- 使用 `title` 属性作为字段注释
|
|
182
|
+
- 使用 `description` 属性作为详细说明
|
|
183
|
+
- 使用 `pattern` 属性定义字符串格式验证
|
|
184
|
+
|
|
185
|
+
### 1.3 生成器特殊处理
|
|
186
|
+
|
|
187
|
+
当前 SDK 生成器会进行以下特殊处理:
|
|
188
|
+
|
|
189
|
+
1. **路径参数转换**:将路径中的 `{paramName}` 转换为 `{paramName}` 并进行驼峰命名
|
|
190
|
+
2. **参数展开**:对于可选参数,会将对象参数展开为多个独立参数
|
|
191
|
+
3. **默认值处理**:为可空字段生成适当的默认值处理逻辑
|
|
192
|
+
4. **导入检测**:自动分析类型依赖关系并生成相应的导入语句
|
|
193
|
+
|
|
194
|
+
### 1.4 完整示例
|
|
195
|
+
|
|
196
|
+
以下是一个完整的 API 端点定义示例:
|
|
197
|
+
|
|
198
|
+
```json
|
|
199
|
+
{
|
|
200
|
+
"openapi": "3.1.0-rc0",
|
|
201
|
+
"info": {
|
|
202
|
+
"title": "Example Service",
|
|
203
|
+
"version": "1.0.0"
|
|
204
|
+
},
|
|
205
|
+
"paths": {
|
|
206
|
+
"/users/{userId}": {
|
|
207
|
+
"get": {
|
|
208
|
+
"summary": "获取用户信息",
|
|
209
|
+
"description": "根据用户ID获取用户详细信息",
|
|
210
|
+
"tags": ["User"],
|
|
211
|
+
"operationId": "getUserById",
|
|
212
|
+
"parameters": [
|
|
213
|
+
{
|
|
214
|
+
"name": "userId",
|
|
215
|
+
"in": "path",
|
|
216
|
+
"required": true,
|
|
217
|
+
"schema": {
|
|
218
|
+
"type": "integer"
|
|
219
|
+
},
|
|
220
|
+
"description": "用户ID"
|
|
221
|
+
},
|
|
222
|
+
{
|
|
223
|
+
"name": "includeProfile",
|
|
224
|
+
"in": "query",
|
|
225
|
+
"required": false,
|
|
226
|
+
"schema": {
|
|
227
|
+
"type": "boolean",
|
|
228
|
+
"default": false
|
|
229
|
+
},
|
|
230
|
+
"description": "是否包含用户档案信息"
|
|
231
|
+
}
|
|
232
|
+
],
|
|
233
|
+
"responses": {
|
|
234
|
+
"200": {
|
|
235
|
+
"description": "成功获取用户信息",
|
|
236
|
+
"content": {
|
|
237
|
+
"application/json": {
|
|
238
|
+
"schema": {
|
|
239
|
+
"$ref": "#/components/schemas/User"
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
},
|
|
248
|
+
"components": {
|
|
249
|
+
"schemas": {
|
|
250
|
+
"User": {
|
|
251
|
+
"type": "object",
|
|
252
|
+
"properties": {
|
|
253
|
+
"id": {
|
|
254
|
+
"type": "integer",
|
|
255
|
+
"title": "用户ID"
|
|
256
|
+
},
|
|
257
|
+
"name": {
|
|
258
|
+
"type": "string",
|
|
259
|
+
"title": "用户名"
|
|
260
|
+
},
|
|
261
|
+
"email": {
|
|
262
|
+
"type": ["string", "null"],
|
|
263
|
+
"title": "邮箱地址"
|
|
264
|
+
},
|
|
265
|
+
"profile": {
|
|
266
|
+
"oneOf": [
|
|
267
|
+
{
|
|
268
|
+
"type": "null"
|
|
269
|
+
},
|
|
270
|
+
{
|
|
271
|
+
"$ref": "#/components/schemas/UserProfile"
|
|
272
|
+
}
|
|
273
|
+
],
|
|
274
|
+
"title": "用户档案"
|
|
275
|
+
}
|
|
276
|
+
},
|
|
277
|
+
"required": ["id", "name"]
|
|
278
|
+
},
|
|
279
|
+
"UserProfile": {
|
|
280
|
+
"type": "object",
|
|
281
|
+
"properties": {
|
|
282
|
+
"age": {
|
|
283
|
+
"type": "integer",
|
|
284
|
+
"title": "年龄"
|
|
285
|
+
},
|
|
286
|
+
"bio": {
|
|
287
|
+
"type": "string",
|
|
288
|
+
"title": "个人简介"
|
|
289
|
+
}
|
|
290
|
+
},
|
|
291
|
+
"required": ["age", "bio"]
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
这个自动化 SDK 生成系统通过解析 OpenAPI 规范,使用模板引擎生成类型安全的 TypeScript 代码,大大减少了手动编写 API 客户端的工作量,同时确保了前端与后端 API 的一致性。
|
package/README.md
ADDED
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
# Swagger Documentation Generator
|
|
2
|
+
|
|
3
|
+
A tool to generate frontend documentation, TypeScript types, and React Hooks from Swagger/OpenAPI JSON files.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
You can install this package globally to use it as a CLI tool:
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install -g swagger-doc-generator
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Or you can use it without installing with npx:
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npx swagger-doc-generator
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Usage
|
|
20
|
+
|
|
21
|
+
### Command Line Interface
|
|
22
|
+
|
|
23
|
+
The tool can be used from the command line to generate documentation, TypeScript types, or React Hooks from a Swagger JSON file:
|
|
24
|
+
|
|
25
|
+
#### Generate Documentation (Default)
|
|
26
|
+
```bash
|
|
27
|
+
# Generate documentation from a URL (output to ./generated/docs/api-documentation.md by default)
|
|
28
|
+
swagger-doc-generator --url https://petstore.swagger.io/v2/swagger.json
|
|
29
|
+
|
|
30
|
+
# Generate documentation from a local file (output to ./generated/docs/api-documentation.md by default)
|
|
31
|
+
swagger-doc-generator --input ./swagger.json
|
|
32
|
+
|
|
33
|
+
# Generate documentation and specify output file
|
|
34
|
+
swagger-doc-generator --url https://petstore.swagger.io/v2/swagger.json --output ./docs/api-documentation.md
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
#### Generate TypeScript Types
|
|
38
|
+
```bash
|
|
39
|
+
# Generate TypeScript types from a local file (output to ./generated/types/ by default)
|
|
40
|
+
swagger-doc-generator --input ./swagger.json --generate-types
|
|
41
|
+
|
|
42
|
+
# Generate TypeScript types with custom output directory
|
|
43
|
+
swagger-doc-generator --input ./swagger.json --generate-types --types-output ./src/types
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
#### Generate React Hooks
|
|
47
|
+
```bash
|
|
48
|
+
# Generate React hooks from a local file (output to ./generated/hooks/ by default)
|
|
49
|
+
swagger-doc-generator --input ./swagger.json --generate-hooks
|
|
50
|
+
|
|
51
|
+
# Generate React hooks with custom output directory
|
|
52
|
+
swagger-doc-generator --input ./swagger.json --generate-hooks --hooks-output ./src/hooks
|
|
53
|
+
|
|
54
|
+
# Generate both types and hooks (output to ./generated/ directories by default)
|
|
55
|
+
swagger-doc-generator --input ./swagger.json --generate-types --generate-hooks
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
#### Default Output Location
|
|
59
|
+
By default, all generated content is placed in the `./generated` directory to keep your project clean and separate generated content from source code:
|
|
60
|
+
|
|
61
|
+
- Documentation: `./generated/docs/api-documentation.md`
|
|
62
|
+
- TypeScript types: `./generated/types/`
|
|
63
|
+
- React hooks: `./generated/hooks/`
|
|
64
|
+
|
|
65
|
+
### Programmatic Usage
|
|
66
|
+
|
|
67
|
+
You can also use this package programmatically in your JavaScript/TypeScript code:
|
|
68
|
+
|
|
69
|
+
```javascript
|
|
70
|
+
const { SwaggerDocGenerator } = require('swagger-doc-generator');
|
|
71
|
+
|
|
72
|
+
async function generate() {
|
|
73
|
+
const generator = new SwaggerDocGenerator();
|
|
74
|
+
|
|
75
|
+
// Load from URL
|
|
76
|
+
const swaggerDoc = await generator.fetchSwaggerJSON('https://api.example.com/swagger.json');
|
|
77
|
+
|
|
78
|
+
// Or load from file
|
|
79
|
+
// const swaggerDoc = generator.loadSwaggerFromFile('./swagger.json');
|
|
80
|
+
|
|
81
|
+
// Generate documentation
|
|
82
|
+
const documentation = generator.generateDocumentation(swaggerDoc);
|
|
83
|
+
generator.saveDocumentationToFile(documentation, './docs/api-documentation.md');
|
|
84
|
+
|
|
85
|
+
// Generate TypeScript types
|
|
86
|
+
const types = generator.generateTypeDefinitions(swaggerDoc);
|
|
87
|
+
generator.saveTypesToFile(types, './types/api-types.ts');
|
|
88
|
+
|
|
89
|
+
// Generate React hooks organized by tags
|
|
90
|
+
const hooksByTag = generator.generateReactHooks(swaggerDoc);
|
|
91
|
+
generator.saveHooksByTag(hooksByTag, './hooks');
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
generate().catch(console.error);
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
## Options
|
|
98
|
+
|
|
99
|
+
- `--url, -u`: URL to the Swagger JSON file
|
|
100
|
+
- `--input, -i`: Path to the local Swagger JSON file
|
|
101
|
+
- `--output, -o`: Output path for the generated documentation (default: ./docs/api-documentation.md)
|
|
102
|
+
- `--generate-types`: Generate TypeScript type definitions
|
|
103
|
+
- `--generate-hooks`: Generate React hooks
|
|
104
|
+
- `--types-output`: Output directory for TypeScript types (default: ./types)
|
|
105
|
+
- `--hooks-output`: Output directory for React hooks (default: ./hooks)
|
|
106
|
+
- `--help`: Show help information
|
|
107
|
+
|
|
108
|
+
## Features
|
|
109
|
+
|
|
110
|
+
- Fetches Swagger JSON from a URL
|
|
111
|
+
- Loads Swagger JSON from a local file
|
|
112
|
+
- Generates comprehensive documentation in Markdown format
|
|
113
|
+
- Generates TypeScript type definitions from schemas
|
|
114
|
+
- Generates React Hooks for API endpoints, organized by tags
|
|
115
|
+
- Supports both Swagger 2.0 and OpenAPI 3.x specifications
|
|
116
|
+
- Handles API endpoints, parameters, request/response bodies, and responses
|
|
117
|
+
- Creates output directory if it doesn't exist
|
|
118
|
+
- Properly handles path and query parameters
|
|
119
|
+
- Generates proper TypeScript interfaces and type aliases
|
|
120
|
+
- Organizes generated code by API tags
|
|
121
|
+
|
|
122
|
+
## Example Output
|
|
123
|
+
|
|
124
|
+
### Generated TypeScript Types
|
|
125
|
+
```typescript
|
|
126
|
+
// Auto-generated TypeScript types from Swagger schema
|
|
127
|
+
|
|
128
|
+
export interface User {
|
|
129
|
+
/** 用户ID */
|
|
130
|
+
id: number;
|
|
131
|
+
/** 用户名 */
|
|
132
|
+
name: string;
|
|
133
|
+
/** 用户邮箱 */
|
|
134
|
+
email: string;
|
|
135
|
+
/** 头像URL */
|
|
136
|
+
avatar?: string;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
export interface UserConfig {
|
|
140
|
+
/** 用户ID */
|
|
141
|
+
userId: number;
|
|
142
|
+
/** 主题设置 */
|
|
143
|
+
theme: "light" | "dark";
|
|
144
|
+
/** 语言设置 */
|
|
145
|
+
language: string;
|
|
146
|
+
/** 通知设置 */
|
|
147
|
+
notifications: NotificationSettings;
|
|
148
|
+
/** 个性化设置 */
|
|
149
|
+
preferences: Preferences;
|
|
150
|
+
}
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
### Generated React Hooks
|
|
154
|
+
```typescript
|
|
155
|
+
// Users API Hooks
|
|
156
|
+
|
|
157
|
+
export interface UserController_queryUserInfoParams {
|
|
158
|
+
id?: number;
|
|
159
|
+
name?: string;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
export const useUserController_queryUserInfo = () => {
|
|
163
|
+
const apiCall = async (params: UserController_queryUserInfoParams) => {
|
|
164
|
+
const path = `${process.env.REACT_APP_API_BASE_URL || ''}/api/queryUserInfo`;
|
|
165
|
+
const queryParams = new URLSearchParams();
|
|
166
|
+
if (params.id) queryParams.append('id', params.id.toString());
|
|
167
|
+
if (params.name) queryParams.append('name', params.name.toString());
|
|
168
|
+
const queryString = queryParams.toString();
|
|
169
|
+
const url = `${path}${queryString ? '?' + queryString : ''}`;
|
|
170
|
+
const options: RequestInit = {
|
|
171
|
+
method: 'GET',
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
const result = await fetch(url, options);
|
|
175
|
+
return result.json() as Promise<User[]>;
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
return { userController_queryUserInfo: apiCall };
|
|
179
|
+
};
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
## Development
|
|
183
|
+
|
|
184
|
+
To run this project locally:
|
|
185
|
+
|
|
186
|
+
1. Clone the repository
|
|
187
|
+
2. Run `pnpm install` to install dependencies
|
|
188
|
+
3. Run `pnpm run build` to compile TypeScript
|
|
189
|
+
4. Run `pnpm run test` to run tests
|
|
190
|
+
|
|
191
|
+
## License
|
|
192
|
+
|
|
193
|
+
MIT
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import { SwaggerDocGenerator, SwaggerDoc } from '../src/index';
|
|
2
|
+
import axios from 'axios';
|
|
3
|
+
import * as fs from 'fs';
|
|
4
|
+
|
|
5
|
+
// Mock swagger doc for testing
|
|
6
|
+
const mockSwaggerDoc: SwaggerDoc = {
|
|
7
|
+
swagger: "2.0",
|
|
8
|
+
info: {
|
|
9
|
+
title: "Test API",
|
|
10
|
+
version: "1.0.0",
|
|
11
|
+
description: "A test API"
|
|
12
|
+
},
|
|
13
|
+
paths: {
|
|
14
|
+
"/test": {
|
|
15
|
+
get: {
|
|
16
|
+
summary: "Get test",
|
|
17
|
+
description: "Returns test data",
|
|
18
|
+
responses: {
|
|
19
|
+
"200": {
|
|
20
|
+
description: "Successful response",
|
|
21
|
+
content: {
|
|
22
|
+
"application/json": {
|
|
23
|
+
schema: {
|
|
24
|
+
type: "object",
|
|
25
|
+
properties: {
|
|
26
|
+
message: {
|
|
27
|
+
type: "string"
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
// Create a mock for the fs module
|
|
41
|
+
jest.mock('fs');
|
|
42
|
+
|
|
43
|
+
describe('SwaggerDocGenerator', () => {
|
|
44
|
+
let generator: SwaggerDocGenerator;
|
|
45
|
+
|
|
46
|
+
beforeEach(() => {
|
|
47
|
+
generator = new SwaggerDocGenerator();
|
|
48
|
+
// Reset all mocks before each test
|
|
49
|
+
jest.clearAllMocks();
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
describe('fetchSwaggerJSON', () => {
|
|
53
|
+
it('should fetch swagger JSON from a URL', async () => {
|
|
54
|
+
const mockUrl = 'https://api.example.com/swagger.json';
|
|
55
|
+
|
|
56
|
+
// Mock the axios response
|
|
57
|
+
jest.spyOn(axios, 'get').mockResolvedValueOnce({ data: mockSwaggerDoc });
|
|
58
|
+
|
|
59
|
+
const result = await generator.fetchSwaggerJSON(mockUrl);
|
|
60
|
+
|
|
61
|
+
expect(result).toEqual(mockSwaggerDoc);
|
|
62
|
+
expect(axios.get).toHaveBeenCalledWith(mockUrl);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it('should handle errors when fetching swagger JSON', async () => {
|
|
66
|
+
const mockUrl = 'https://api.example.com/invalid.json';
|
|
67
|
+
|
|
68
|
+
// Mock the axios response to reject
|
|
69
|
+
jest.spyOn(axios, 'get').mockRejectedValueOnce(new Error('Network error'));
|
|
70
|
+
|
|
71
|
+
await expect(generator.fetchSwaggerJSON(mockUrl)).rejects.toThrow(
|
|
72
|
+
`Failed to fetch Swagger JSON from ${mockUrl}: Network error`
|
|
73
|
+
);
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
describe('loadSwaggerFromFile', () => {
|
|
78
|
+
it('should load swagger JSON from a local file', () => {
|
|
79
|
+
const mockFilePath = 'test-swagger.json';
|
|
80
|
+
|
|
81
|
+
// Mock the file system
|
|
82
|
+
(fs.readFileSync as jest.Mock).mockReturnValue(JSON.stringify(mockSwaggerDoc));
|
|
83
|
+
|
|
84
|
+
const result = generator.loadSwaggerFromFile(mockFilePath);
|
|
85
|
+
|
|
86
|
+
expect(result).toEqual(mockSwaggerDoc);
|
|
87
|
+
expect(fs.readFileSync).toHaveBeenCalledWith(mockFilePath, 'utf8');
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
it('should handle errors when loading swagger JSON from file', () => {
|
|
91
|
+
const mockFilePath = 'nonexistent.json';
|
|
92
|
+
|
|
93
|
+
// Mock the file system to throw an error
|
|
94
|
+
(fs.readFileSync as jest.Mock).mockImplementation(() => {
|
|
95
|
+
throw new Error('File not found');
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
expect(() => generator.loadSwaggerFromFile(mockFilePath)).toThrow(
|
|
99
|
+
`Failed to load Swagger JSON from ${mockFilePath}: File not found`
|
|
100
|
+
);
|
|
101
|
+
});
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
describe('generateDocumentation', () => {
|
|
105
|
+
it('should generate documentation from swagger doc', () => {
|
|
106
|
+
const result = generator.generateDocumentation(mockSwaggerDoc);
|
|
107
|
+
|
|
108
|
+
expect(result).toContain('# Test API');
|
|
109
|
+
expect(result).toContain('A test API v1.0.0');
|
|
110
|
+
expect(result).toContain('## API Endpoints');
|
|
111
|
+
expect(result).toContain('### GET /test');
|
|
112
|
+
expect(result).toContain('**Summary:** Get test');
|
|
113
|
+
expect(result).toContain('**Description:** Returns test data');
|
|
114
|
+
expect(result).toContain('**Responses:**');
|
|
115
|
+
expect(result).toContain('- **200**: Successful response');
|
|
116
|
+
});
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
describe('saveDocumentationToFile', () => {
|
|
120
|
+
it('should save documentation to a file', () => {
|
|
121
|
+
const mockDocumentation = '# Test Documentation';
|
|
122
|
+
const mockOutputPath = './docs/test.md';
|
|
123
|
+
|
|
124
|
+
// Mock the file system methods
|
|
125
|
+
(fs.existsSync as jest.Mock).mockReturnValue(true);
|
|
126
|
+
(fs.writeFileSync as jest.Mock).mockImplementation();
|
|
127
|
+
(fs.mkdirSync as jest.Mock).mockImplementation();
|
|
128
|
+
|
|
129
|
+
generator.saveDocumentationToFile(mockDocumentation, mockOutputPath);
|
|
130
|
+
|
|
131
|
+
expect(fs.existsSync).toHaveBeenCalledWith('./docs');
|
|
132
|
+
expect(fs.writeFileSync).toHaveBeenCalledWith(mockOutputPath, mockDocumentation, 'utf8');
|
|
133
|
+
expect(fs.mkdirSync).not.toHaveBeenCalled(); // Because directory exists
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
it('should create directory if it does not exist', () => {
|
|
137
|
+
const mockDocumentation = '# Test Documentation';
|
|
138
|
+
const mockOutputPath = './newdir/test.md';
|
|
139
|
+
|
|
140
|
+
// Mock the file system methods
|
|
141
|
+
(fs.existsSync as jest.Mock).mockReturnValue(false);
|
|
142
|
+
(fs.writeFileSync as jest.Mock).mockImplementation();
|
|
143
|
+
(fs.mkdirSync as jest.Mock).mockImplementation();
|
|
144
|
+
|
|
145
|
+
generator.saveDocumentationToFile(mockDocumentation, mockOutputPath);
|
|
146
|
+
|
|
147
|
+
expect(fs.existsSync).toHaveBeenCalledWith('./newdir');
|
|
148
|
+
expect(fs.mkdirSync).toHaveBeenCalledWith('./newdir', { recursive: true });
|
|
149
|
+
expect(fs.writeFileSync).toHaveBeenCalledWith(mockOutputPath, mockDocumentation, 'utf8');
|
|
150
|
+
});
|
|
151
|
+
});
|
|
152
|
+
});
|
package/dist/cli.d.ts
ADDED