swgto-ts 2.0.0 → 2.0.1
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 +147 -147
- package/dist/{chunk-4OK6R2K5.js → chunk-ZURWUVPF.js} +80 -16
- package/dist/cli.js +18 -1
- package/dist/index.d.ts +21 -9
- package/dist/index.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,147 +1,147 @@
|
|
|
1
|
-
# swgto
|
|
2
|
-
|
|
3
|
-
`swgto` 是一个把 `OpenAPI 3.x` 文档转换成项目内请求函数的 Node.js 库和 CLI。
|
|
4
|
-
|
|
5
|
-
安装后会注册一个 `swgto` 命令。执行时它会在当前目录查找 `.swaggerts.config.ts` 或 `.swaggerts.config.js`,然后生成:
|
|
6
|
-
|
|
7
|
-
- 按路径前缀分组的请求文件
|
|
8
|
-
- 按文档模块输出请求文件。单文档默认输出到 `services/`,多文档时多个模块目录平级
|
|
9
|
-
- `outputDir/index.ts` 或 `outputDir/index.js`
|
|
10
|
-
- 类型文件 `outputDir/types.ts` 或带 JSDoc 的 `outputDir/types.js`
|
|
11
|
-
|
|
12
|
-
## 安装
|
|
13
|
-
|
|
14
|
-
```bash
|
|
15
|
-
npm install swgto-ts
|
|
16
|
-
```
|
|
17
|
-
|
|
18
|
-
## 配置
|
|
19
|
-
|
|
20
|
-
在项目根目录创建 `.swaggerts.config.ts`:
|
|
21
|
-
|
|
22
|
-
```ts
|
|
23
|
-
import type { SwaggerTsConfig } from 'swgto-ts'
|
|
24
|
-
|
|
25
|
-
const config: SwaggerTsConfig = {
|
|
26
|
-
docUrls: 'https://example.com/openapi.json',
|
|
27
|
-
httpClientPath: '@/utils/request',
|
|
28
|
-
outputDir: 'src/api',
|
|
29
|
-
outputType: 'ts',
|
|
30
|
-
typeName: 'types',
|
|
31
|
-
cleanOutput: true,
|
|
32
|
-
renameMethod: (apiPath, method) => `${method}_${apiPath.replace(/[\\/{}]/g, '_')}`,
|
|
33
|
-
resolveRequestPath: (apiPath, method) => `/proxy${apiPath}`,
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
export default config
|
|
37
|
-
```
|
|
38
|
-
|
|
39
|
-
多文档模式下需要提供 `moduleName`:
|
|
40
|
-
|
|
41
|
-
```ts
|
|
42
|
-
export default {
|
|
43
|
-
docUrls: ['https://example.com/user-openapi.json', 'https://example.com/order-openapi.json'],
|
|
44
|
-
httpClientPath: '@/utils/request',
|
|
45
|
-
outputDir: 'src/api',
|
|
46
|
-
moduleName: (docUrl) => (docUrl.includes('user') ? 'user' : 'order'),
|
|
47
|
-
}
|
|
48
|
-
```
|
|
49
|
-
|
|
50
|
-
## 使用
|
|
51
|
-
|
|
52
|
-
```bash
|
|
53
|
-
swgto
|
|
54
|
-
```
|
|
55
|
-
|
|
56
|
-
## 输出示例
|
|
57
|
-
|
|
58
|
-
```text
|
|
59
|
-
src/api/
|
|
60
|
-
index.ts
|
|
61
|
-
types.ts
|
|
62
|
-
services/
|
|
63
|
-
get_user_list.ts
|
|
64
|
-
post_user_create.ts
|
|
65
|
-
```
|
|
66
|
-
|
|
67
|
-
## 配置项
|
|
68
|
-
|
|
69
|
-
```ts
|
|
70
|
-
export interface SwaggerTsConfig {
|
|
71
|
-
docUrls: string | string[]
|
|
72
|
-
httpClientPath: string
|
|
73
|
-
renameMethod?: (path: string, method: string) => string
|
|
74
|
-
resolveRequestPath?: (path: string, method: string, docUrl: string) => string
|
|
75
|
-
ignoreUrl?: (path: string, method: string, docUrl: string) => boolean
|
|
76
|
-
outputDir?: string
|
|
77
|
-
moduleName?: (docUrl: string) => string
|
|
78
|
-
outputType?: 'ts' | 'js'
|
|
79
|
-
typeName?: string
|
|
80
|
-
cleanOutput?: boolean
|
|
81
|
-
fileNaming?: 'module' | 'path'
|
|
82
|
-
flattenQueryParam?: boolean
|
|
83
|
-
mergeParams?: boolean
|
|
84
|
-
}
|
|
85
|
-
```
|
|
86
|
-
|
|
87
|
-
### 字段说明
|
|
88
|
-
|
|
89
|
-
| 字段 | 类型 | 默认值 | 说明 |
|
|
90
|
-
| -------------------- | ----------------------------------- | ------------ | -------------------------------------------------------------------------------------------------------------------------------------- |
|
|
91
|
-
| `docUrls` | `string \| string[]` | (必填) | OpenAPI 3.x 文档的 URL,支持单文档字符串或多文档数组 |
|
|
92
|
-
| `httpClientPath` | `string` | (必填) | 项目内 HTTP 请求工具的引入路径,生成的文件会从中 `import request` |
|
|
93
|
-
| `outputDir` | `string` | `'src/api'` | 代码输出目录,相对项目根目录 |
|
|
94
|
-
| `outputType` | `'ts' \| 'js'` | `'ts'` | 输出文件类型,`'ts'` 生成 `.ts` 文件,`'js'` 生成 `.js` 文件(带 JSDoc 类型注释) |
|
|
95
|
-
| `typeName` | `string` | `'types'` | 类型文件名(不含后缀),如 `'types'` 生成 `types.ts` 或 `types.js` |
|
|
96
|
-
| `fileNaming` | `'module' \| 'path'` | `'path'` | 文件组织方式:`'path'` 按接口路径每个文件一个函数;`'module'` 按控制器合并到同一个文件 |
|
|
97
|
-
| `moduleName` | `(docUrl) => string` | `'services'` | 多文档时,为每个文档指定模块目录名。多文档模式下**必填** |
|
|
98
|
-
| `renameMethod` | `(path, method) => string` | 见说明 | 自定义函数名生成规则。默认根据路径和方法名自动生成(如 `get_user_list`) |
|
|
99
|
-
| `resolveRequestPath` | `(path, method, docUrl) => string` | 原路径 | 自定义请求路径转换规则,可用于添加统一前缀等,如 `(path) => \`/proxy\${path}\`` |
|
|
100
|
-
| `ignoreUrl` | `(path, method, docUrl) => boolean` | 不过滤 | 过滤不需要生成代码的接口,返回 `true` 跳过当前接口 |
|
|
101
|
-
| `cleanOutput` | `boolean` | `false` | 生成前是否清空输出目录 |
|
|
102
|
-
| `flattenQueryParam` | `boolean` | `false` | 当 query 参数只有一个且为 `$ref` 引用类型时,直接用引用类型代替 `{ query: RefType }` |
|
|
103
|
-
| `mergeParams` | `boolean` | `false` | 合并参数层级:`true` 时展平 `path/query/body` 嵌套,直接使用 `params: { field1, field2 }` 代替 `params: { path: {...}, query: {...} }` |
|
|
104
|
-
|
|
105
|
-
### 函数名生成规则
|
|
106
|
-
|
|
107
|
-
默认函数名格式为 `{method}_{path_segments}`,如:
|
|
108
|
-
|
|
109
|
-
- `GET /user/list` → `get_user_list`
|
|
110
|
-
- `POST /user/create` → `post_user_create`
|
|
111
|
-
|
|
112
|
-
可通过 `renameMethod` 自定义:
|
|
113
|
-
|
|
114
|
-
```ts
|
|
115
|
-
renameMethod: (path, method) => `${method}_${path.replace(/[\\/{}]/g, '_')}`,
|
|
116
|
-
```
|
|
117
|
-
|
|
118
|
-
### 文件组织方式
|
|
119
|
-
|
|
120
|
-
**`fileNaming: 'path'`(默认)**— 每个接口生成独立文件:
|
|
121
|
-
|
|
122
|
-
```
|
|
123
|
-
src/api/moduleA/
|
|
124
|
-
get_user_list.ts
|
|
125
|
-
post_user_create.ts
|
|
126
|
-
get_user_detail.ts
|
|
127
|
-
```
|
|
128
|
-
|
|
129
|
-
**`fileNaming: 'module'`**— 按控制器合并文件(自动识别 RESTful 资源路径分组):
|
|
130
|
-
|
|
131
|
-
```
|
|
132
|
-
src/api/moduleA/
|
|
133
|
-
user.ts // 包含 get_user_list, post_user_create, get_user_detail
|
|
134
|
-
order.ts // 包含 get_order_list, post_order_create
|
|
135
|
-
```
|
|
136
|
-
|
|
137
|
-
### 参数合并模式
|
|
138
|
-
|
|
139
|
-
`mergeParams: true` 时展平参数层级,适用于不想嵌套 `path/query/body` 的场景:
|
|
140
|
-
|
|
141
|
-
```ts
|
|
142
|
-
// mergeParams: false(默认),参数按来源嵌套
|
|
143
|
-
getUser({ path: { id: '1' }, query: { name: 'foo' } })
|
|
144
|
-
|
|
145
|
-
// mergeParams: true,参数展平
|
|
146
|
-
getUser({ id: '1', name: 'foo' })
|
|
147
|
-
```
|
|
1
|
+
# swgto
|
|
2
|
+
|
|
3
|
+
`swgto` 是一个把 `OpenAPI 3.x` 文档转换成项目内请求函数的 Node.js 库和 CLI。
|
|
4
|
+
|
|
5
|
+
安装后会注册一个 `swgto` 命令。执行时它会在当前目录查找 `.swaggerts.config.ts` 或 `.swaggerts.config.js`,然后生成:
|
|
6
|
+
|
|
7
|
+
- 按路径前缀分组的请求文件
|
|
8
|
+
- 按文档模块输出请求文件。单文档默认输出到 `services/`,多文档时多个模块目录平级
|
|
9
|
+
- `outputDir/index.ts` 或 `outputDir/index.js`
|
|
10
|
+
- 类型文件 `outputDir/types.ts` 或带 JSDoc 的 `outputDir/types.js`
|
|
11
|
+
|
|
12
|
+
## 安装
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
npm install swgto-ts
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## 配置
|
|
19
|
+
|
|
20
|
+
在项目根目录创建 `.swaggerts.config.ts`:
|
|
21
|
+
|
|
22
|
+
```ts
|
|
23
|
+
import type { SwaggerTsConfig } from 'swgto-ts'
|
|
24
|
+
|
|
25
|
+
const config: SwaggerTsConfig = {
|
|
26
|
+
docUrls: 'https://example.com/openapi.json',
|
|
27
|
+
httpClientPath: '@/utils/request',
|
|
28
|
+
outputDir: 'src/api',
|
|
29
|
+
outputType: 'ts',
|
|
30
|
+
typeName: 'types',
|
|
31
|
+
cleanOutput: true,
|
|
32
|
+
renameMethod: (apiPath, method) => `${method}_${apiPath.replace(/[\\/{}]/g, '_')}`,
|
|
33
|
+
resolveRequestPath: (apiPath, method) => `/proxy${apiPath}`,
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export default config
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
多文档模式下需要提供 `moduleName`:
|
|
40
|
+
|
|
41
|
+
```ts
|
|
42
|
+
export default {
|
|
43
|
+
docUrls: ['https://example.com/user-openapi.json', 'https://example.com/order-openapi.json'],
|
|
44
|
+
httpClientPath: '@/utils/request',
|
|
45
|
+
outputDir: 'src/api',
|
|
46
|
+
moduleName: (docUrl) => (docUrl.includes('user') ? 'user' : 'order'),
|
|
47
|
+
}
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## 使用
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
swgto
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## 输出示例
|
|
57
|
+
|
|
58
|
+
```text
|
|
59
|
+
src/api/
|
|
60
|
+
index.ts
|
|
61
|
+
types.ts
|
|
62
|
+
services/
|
|
63
|
+
get_user_list.ts
|
|
64
|
+
post_user_create.ts
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## 配置项
|
|
68
|
+
|
|
69
|
+
```ts
|
|
70
|
+
export interface SwaggerTsConfig {
|
|
71
|
+
docUrls: string | string[]
|
|
72
|
+
httpClientPath: string
|
|
73
|
+
renameMethod?: (path: string, method: string) => string
|
|
74
|
+
resolveRequestPath?: (path: string, method: string, docUrl: string) => string
|
|
75
|
+
ignoreUrl?: (path: string, method: string, docUrl: string) => boolean
|
|
76
|
+
outputDir?: string
|
|
77
|
+
moduleName?: (docUrl: string) => string
|
|
78
|
+
outputType?: 'ts' | 'js'
|
|
79
|
+
typeName?: string
|
|
80
|
+
cleanOutput?: boolean
|
|
81
|
+
fileNaming?: 'module' | 'path'
|
|
82
|
+
flattenQueryParam?: boolean
|
|
83
|
+
mergeParams?: boolean
|
|
84
|
+
}
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
### 字段说明
|
|
88
|
+
|
|
89
|
+
| 字段 | 类型 | 默认值 | 说明 |
|
|
90
|
+
| -------------------- | ----------------------------------- | ------------ | -------------------------------------------------------------------------------------------------------------------------------------- |
|
|
91
|
+
| `docUrls` | `string \| string[]` | (必填) | OpenAPI 3.x 文档的 URL,支持单文档字符串或多文档数组 |
|
|
92
|
+
| `httpClientPath` | `string` | (必填) | 项目内 HTTP 请求工具的引入路径,生成的文件会从中 `import request` |
|
|
93
|
+
| `outputDir` | `string` | `'src/api'` | 代码输出目录,相对项目根目录 |
|
|
94
|
+
| `outputType` | `'ts' \| 'js'` | `'ts'` | 输出文件类型,`'ts'` 生成 `.ts` 文件,`'js'` 生成 `.js` 文件(带 JSDoc 类型注释) |
|
|
95
|
+
| `typeName` | `string` | `'types'` | 类型文件名(不含后缀),如 `'types'` 生成 `types.ts` 或 `types.js` |
|
|
96
|
+
| `fileNaming` | `'module' \| 'path'` | `'path'` | 文件组织方式:`'path'` 按接口路径每个文件一个函数;`'module'` 按控制器合并到同一个文件 |
|
|
97
|
+
| `moduleName` | `(docUrl) => string` | `'services'` | 多文档时,为每个文档指定模块目录名。多文档模式下**必填** |
|
|
98
|
+
| `renameMethod` | `(path, method) => string` | 见说明 | 自定义函数名生成规则。默认根据路径和方法名自动生成(如 `get_user_list`) |
|
|
99
|
+
| `resolveRequestPath` | `(path, method, docUrl) => string` | 原路径 | 自定义请求路径转换规则,可用于添加统一前缀等,如 `(path) => \`/proxy\${path}\`` |
|
|
100
|
+
| `ignoreUrl` | `(path, method, docUrl) => boolean` | 不过滤 | 过滤不需要生成代码的接口,返回 `true` 跳过当前接口 |
|
|
101
|
+
| `cleanOutput` | `boolean` | `false` | 生成前是否清空输出目录 |
|
|
102
|
+
| `flattenQueryParam` | `boolean` | `false` | 当 query 参数只有一个且为 `$ref` 引用类型时,直接用引用类型代替 `{ query: RefType }` |
|
|
103
|
+
| `mergeParams` | `boolean` | `false` | 合并参数层级:`true` 时展平 `path/query/body` 嵌套,直接使用 `params: { field1, field2 }` 代替 `params: { path: {...}, query: {...} }` |
|
|
104
|
+
|
|
105
|
+
### 函数名生成规则
|
|
106
|
+
|
|
107
|
+
默认函数名格式为 `{method}_{path_segments}`,如:
|
|
108
|
+
|
|
109
|
+
- `GET /user/list` → `get_user_list`
|
|
110
|
+
- `POST /user/create` → `post_user_create`
|
|
111
|
+
|
|
112
|
+
可通过 `renameMethod` 自定义:
|
|
113
|
+
|
|
114
|
+
```ts
|
|
115
|
+
renameMethod: (path, method) => `${method}_${path.replace(/[\\/{}]/g, '_')}`,
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
### 文件组织方式
|
|
119
|
+
|
|
120
|
+
**`fileNaming: 'path'`(默认)**— 每个接口生成独立文件:
|
|
121
|
+
|
|
122
|
+
```
|
|
123
|
+
src/api/moduleA/
|
|
124
|
+
get_user_list.ts
|
|
125
|
+
post_user_create.ts
|
|
126
|
+
get_user_detail.ts
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
**`fileNaming: 'module'`**— 按控制器合并文件(自动识别 RESTful 资源路径分组):
|
|
130
|
+
|
|
131
|
+
```
|
|
132
|
+
src/api/moduleA/
|
|
133
|
+
user.ts // 包含 get_user_list, post_user_create, get_user_detail
|
|
134
|
+
order.ts // 包含 get_order_list, post_order_create
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
### 参数合并模式
|
|
138
|
+
|
|
139
|
+
`mergeParams: true` 时展平参数层级,适用于不想嵌套 `path/query/body` 的场景:
|
|
140
|
+
|
|
141
|
+
```ts
|
|
142
|
+
// mergeParams: false(默认),参数按来源嵌套
|
|
143
|
+
getUser({ path: { id: '1' }, query: { name: 'foo' } })
|
|
144
|
+
|
|
145
|
+
// mergeParams: true,参数展平
|
|
146
|
+
getUser({ id: '1', name: 'foo' })
|
|
147
|
+
```
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// src/generate.ts
|
|
2
|
-
import
|
|
2
|
+
import path4 from "path";
|
|
3
3
|
|
|
4
4
|
// src/config/loadConfig.ts
|
|
5
5
|
import { existsSync } from "fs";
|
|
@@ -74,8 +74,8 @@ function getPathMethods(operations) {
|
|
|
74
74
|
}
|
|
75
75
|
return map;
|
|
76
76
|
}
|
|
77
|
-
function deriveControllerName(
|
|
78
|
-
const segments =
|
|
77
|
+
function deriveControllerName(path5, allPaths, pathMethods) {
|
|
78
|
+
const segments = path5.split("/").filter(Boolean);
|
|
79
79
|
if (segments.length === 0) return "index";
|
|
80
80
|
if (segments.length === 1) return segments[0];
|
|
81
81
|
let effectiveLen = segments.length;
|
|
@@ -98,14 +98,14 @@ function deriveControllerName(path4, allPaths, pathMethods) {
|
|
|
98
98
|
return segments[depth - 1];
|
|
99
99
|
}
|
|
100
100
|
const hasDirectSibling = [...allPaths].some((p) => {
|
|
101
|
-
if (p ===
|
|
101
|
+
if (p === path5) return false;
|
|
102
102
|
if (!p.startsWith(ancestorPath + "/")) return false;
|
|
103
103
|
const remaining = p.slice(ancestorPath.length + 1);
|
|
104
104
|
return !remaining.includes("/");
|
|
105
105
|
});
|
|
106
106
|
if (hasDirectSibling) {
|
|
107
107
|
const siblingIsRest = [...allPaths].some((p) => {
|
|
108
|
-
if (p ===
|
|
108
|
+
if (p === path5) return false;
|
|
109
109
|
if (!p.startsWith(ancestorPath + "/")) return false;
|
|
110
110
|
const remaining = p.slice(ancestorPath.length + 1);
|
|
111
111
|
if (remaining.includes("/")) return false;
|
|
@@ -120,7 +120,7 @@ function deriveControllerName(path4, allPaths, pathMethods) {
|
|
|
120
120
|
}
|
|
121
121
|
const currentPath = "/" + segments.slice(0, depth + 1).join("/");
|
|
122
122
|
const hasChildren = [...allPaths].some(
|
|
123
|
-
(p) => p !==
|
|
123
|
+
(p) => p !== path5 && p.startsWith(currentPath + "/")
|
|
124
124
|
);
|
|
125
125
|
if (hasChildren) {
|
|
126
126
|
return resultName(segments, depth);
|
|
@@ -676,6 +676,64 @@ async function removeDir(dirPath) {
|
|
|
676
676
|
}
|
|
677
677
|
}
|
|
678
678
|
|
|
679
|
+
// src/utils/snapshot.ts
|
|
680
|
+
import path3 from "path";
|
|
681
|
+
import { readFile, writeFile as writeFile2 } from "fs/promises";
|
|
682
|
+
function buildKey(op) {
|
|
683
|
+
return `${op.docUrl}|${op.method}|${op.path}`;
|
|
684
|
+
}
|
|
685
|
+
function toEntry(op) {
|
|
686
|
+
return {
|
|
687
|
+
key: buildKey(op),
|
|
688
|
+
method: op.method,
|
|
689
|
+
path: op.path,
|
|
690
|
+
functionName: op.functionName,
|
|
691
|
+
summary: op.summary,
|
|
692
|
+
moduleName: op.moduleName
|
|
693
|
+
};
|
|
694
|
+
}
|
|
695
|
+
async function loadSnapshot(snapshotPath) {
|
|
696
|
+
const map = /* @__PURE__ */ new Map();
|
|
697
|
+
try {
|
|
698
|
+
const raw = await readFile(snapshotPath, "utf8");
|
|
699
|
+
const data = JSON.parse(raw);
|
|
700
|
+
if (data.version === 1) {
|
|
701
|
+
for (const entry of data.operations) {
|
|
702
|
+
map.set(entry.key, entry);
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
} catch {
|
|
706
|
+
}
|
|
707
|
+
return map;
|
|
708
|
+
}
|
|
709
|
+
async function saveSnapshot(snapshotPath, operations) {
|
|
710
|
+
const snapshot = {
|
|
711
|
+
version: 1,
|
|
712
|
+
operations: operations.map(toEntry)
|
|
713
|
+
};
|
|
714
|
+
await ensureDir(path3.dirname(snapshotPath));
|
|
715
|
+
await writeFile2(snapshotPath, JSON.stringify(snapshot, null, 2), "utf8");
|
|
716
|
+
}
|
|
717
|
+
function compareSnapshot(previous, current) {
|
|
718
|
+
const currentKeys = /* @__PURE__ */ new Set();
|
|
719
|
+
const newOperations = [];
|
|
720
|
+
for (const op of current) {
|
|
721
|
+
const key = buildKey(op);
|
|
722
|
+
currentKeys.add(key);
|
|
723
|
+
if (!previous.has(key)) {
|
|
724
|
+
newOperations.push(toEntry(op));
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
const removedOperations = [];
|
|
728
|
+
for (const [key, entry] of previous) {
|
|
729
|
+
if (!currentKeys.has(key)) {
|
|
730
|
+
removedOperations.push(entry);
|
|
731
|
+
}
|
|
732
|
+
}
|
|
733
|
+
return { newOperations, removedOperations };
|
|
734
|
+
}
|
|
735
|
+
var SNAPSHOT_FILE = ".swaggerts.cache.json";
|
|
736
|
+
|
|
679
737
|
// src/generate.ts
|
|
680
738
|
function getRootImportPath(typeName) {
|
|
681
739
|
return `../${typeName}`;
|
|
@@ -684,8 +742,10 @@ async function generateFromConfig(cwd = process.cwd()) {
|
|
|
684
742
|
const { configPath, config } = await loadConfig(cwd);
|
|
685
743
|
const documentMap = /* @__PURE__ */ new Map();
|
|
686
744
|
const operations = [];
|
|
745
|
+
const snapshotPath = path4.join(cwd, config.outputDir, SNAPSHOT_FILE);
|
|
746
|
+
const previousSnapshot = await loadSnapshot(snapshotPath);
|
|
687
747
|
if (config.cleanOutput) {
|
|
688
|
-
await removeDir(
|
|
748
|
+
await removeDir(path4.join(cwd, config.outputDir));
|
|
689
749
|
console.log(`Cleaned output directory: ${config.outputDir}`);
|
|
690
750
|
}
|
|
691
751
|
for (const docUrl of config.docUrls) {
|
|
@@ -694,13 +754,14 @@ async function generateFromConfig(cwd = process.cwd()) {
|
|
|
694
754
|
operations.push(...parsePaths(document, docUrl, config));
|
|
695
755
|
}
|
|
696
756
|
const grouped = groupByPrefix(operations);
|
|
757
|
+
const { newOperations, removedOperations } = compareSnapshot(previousSnapshot, operations);
|
|
697
758
|
const files = [];
|
|
698
759
|
for (const [moduleName, moduleOperations] of Object.entries(grouped)) {
|
|
699
760
|
if (config.fileNaming === "module") {
|
|
700
761
|
const controllerMap = groupByController(moduleOperations);
|
|
701
762
|
for (const [controllerName, controllerOperations] of Object.entries(controllerMap)) {
|
|
702
|
-
const relativeFile =
|
|
703
|
-
const absoluteFile =
|
|
763
|
+
const relativeFile = path4.join(config.outputDir, moduleName, `${controllerName}.${config.outputType}`);
|
|
764
|
+
const absoluteFile = path4.join(cwd, relativeFile);
|
|
704
765
|
const content = config.outputType === "ts" ? generateTsModuleFile(controllerOperations, config.httpClientPath, getRootImportPath(config.typeName), config.mergeParams) : generateJsModuleFile(controllerOperations, config.httpClientPath, getRootImportPath(config.typeName), config.mergeParams);
|
|
705
766
|
for (const op of controllerOperations) {
|
|
706
767
|
op.fileBaseName = controllerName;
|
|
@@ -710,8 +771,8 @@ async function generateFromConfig(cwd = process.cwd()) {
|
|
|
710
771
|
}
|
|
711
772
|
} else {
|
|
712
773
|
for (const operation of moduleOperations) {
|
|
713
|
-
const relativeFile =
|
|
714
|
-
const absoluteFile =
|
|
774
|
+
const relativeFile = path4.join(config.outputDir, moduleName, `${operation.fileBaseName}.${config.outputType}`);
|
|
775
|
+
const absoluteFile = path4.join(cwd, relativeFile);
|
|
715
776
|
const content = config.outputType === "ts" ? generateTsRequestFile(operation, config.httpClientPath, getRootImportPath(config.typeName), config.mergeParams) : generateJsRequestFile(operation, config.httpClientPath, getRootImportPath(config.typeName), config.mergeParams);
|
|
716
777
|
await writeTextFile(absoluteFile, content);
|
|
717
778
|
files.push(relativeFile);
|
|
@@ -720,21 +781,24 @@ async function generateFromConfig(cwd = process.cwd()) {
|
|
|
720
781
|
}
|
|
721
782
|
const typesContent = generateTypesFile(documentMap, operations, config);
|
|
722
783
|
const indexContent = generateIndexFile(operations, config);
|
|
723
|
-
const typesFile =
|
|
724
|
-
const indexFile =
|
|
784
|
+
const typesFile = path4.join(cwd, config.outputDir, `${config.typeName}.${config.outputType === "ts" ? "ts" : "js"}`);
|
|
785
|
+
const indexFile = path4.join(cwd, config.outputDir, `index.${config.outputType}`);
|
|
725
786
|
await writeTextFile(typesFile, typesContent);
|
|
726
787
|
await writeTextFile(indexFile, indexContent);
|
|
727
788
|
files.push(
|
|
728
|
-
|
|
729
|
-
|
|
789
|
+
path4.relative(cwd, typesFile),
|
|
790
|
+
path4.relative(cwd, indexFile)
|
|
730
791
|
);
|
|
792
|
+
await saveSnapshot(snapshotPath, operations);
|
|
731
793
|
return {
|
|
732
794
|
configPath,
|
|
733
795
|
files,
|
|
734
796
|
operationCount: operations.length,
|
|
735
797
|
moduleCount: Object.keys(grouped).length,
|
|
736
|
-
apiFileCount: files.length - 2
|
|
798
|
+
apiFileCount: files.length - 2,
|
|
737
799
|
// exclude types + index
|
|
800
|
+
newOperations,
|
|
801
|
+
removedOperations
|
|
738
802
|
};
|
|
739
803
|
}
|
|
740
804
|
|
package/dist/cli.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
generateFromConfig
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-ZURWUVPF.js";
|
|
5
5
|
|
|
6
6
|
// src/cli.ts
|
|
7
7
|
async function main() {
|
|
@@ -13,6 +13,23 @@ async function main() {
|
|
|
13
13
|
console.log(`Generated ${result.apiFileCount} api file(s), ${result.operationCount} operation(s) in ${result.moduleCount} module(s).`);
|
|
14
14
|
console.log(`Wrote ${result.files.length} file(s) total (including types, index).`);
|
|
15
15
|
console.log(`Done in ${elapsedMs}ms.`);
|
|
16
|
+
if (result.newOperations.length > 0) {
|
|
17
|
+
console.log(`
|
|
18
|
+
\u65B0\u589E ${result.newOperations.length} \u4E2A API:`);
|
|
19
|
+
const maxWidth = Math.max(...result.newOperations.map((op) => `${op.method.toUpperCase()} ${op.path}`.length));
|
|
20
|
+
for (const op of result.newOperations) {
|
|
21
|
+
const label = `${op.method.toUpperCase()} ${op.path}`.padEnd(maxWidth);
|
|
22
|
+
const desc = op.summary ? ` ${op.summary}` : "";
|
|
23
|
+
console.log(` ${label} -> ${op.functionName}${desc}`);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
if (result.removedOperations.length > 0) {
|
|
27
|
+
console.log(`
|
|
28
|
+
\u79FB\u9664 ${result.removedOperations.length} \u4E2A API:`);
|
|
29
|
+
for (const op of result.removedOperations) {
|
|
30
|
+
console.log(` ${op.method.toUpperCase()} ${op.path} (${op.functionName})`);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
16
33
|
} catch (error) {
|
|
17
34
|
const message = error instanceof Error ? error.message : String(error);
|
|
18
35
|
console.error(`swgto failed: ${message}`);
|
package/dist/index.d.ts
CHANGED
|
@@ -1,12 +1,3 @@
|
|
|
1
|
-
interface GenerateResult {
|
|
2
|
-
configPath: string;
|
|
3
|
-
files: string[];
|
|
4
|
-
operationCount: number;
|
|
5
|
-
moduleCount: number;
|
|
6
|
-
apiFileCount: number;
|
|
7
|
-
}
|
|
8
|
-
declare function generateFromConfig(cwd?: string): Promise<GenerateResult>;
|
|
9
|
-
|
|
10
1
|
type OutputType = 'ts' | 'js';
|
|
11
2
|
type RequestConfig = Record<string, unknown>;
|
|
12
3
|
interface SwaggerTsConfig {
|
|
@@ -25,4 +16,25 @@ interface SwaggerTsConfig {
|
|
|
25
16
|
mergeParams?: boolean;
|
|
26
17
|
}
|
|
27
18
|
|
|
19
|
+
interface SnapshotEntry {
|
|
20
|
+
/** Unique key: docUrl | method | path */
|
|
21
|
+
key: string;
|
|
22
|
+
method: string;
|
|
23
|
+
path: string;
|
|
24
|
+
functionName: string;
|
|
25
|
+
summary?: string;
|
|
26
|
+
moduleName: string;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
interface GenerateResult {
|
|
30
|
+
configPath: string;
|
|
31
|
+
files: string[];
|
|
32
|
+
operationCount: number;
|
|
33
|
+
moduleCount: number;
|
|
34
|
+
apiFileCount: number;
|
|
35
|
+
newOperations: SnapshotEntry[];
|
|
36
|
+
removedOperations: SnapshotEntry[];
|
|
37
|
+
}
|
|
38
|
+
declare function generateFromConfig(cwd?: string): Promise<GenerateResult>;
|
|
39
|
+
|
|
28
40
|
export { type GenerateResult, type RequestConfig, type SwaggerTsConfig, generateFromConfig };
|
package/dist/index.js
CHANGED