swgto-ts 0.1.0 → 2.0.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/README.md +147 -86
- package/dist/{chunk-RRFCLHNU.js → chunk-4OK6R2K5.js} +362 -179
- package/dist/cli.js +3 -3
- package/dist/index.d.ts +5 -0
- package/dist/index.js +1 -1
- package/package.json +5 -3
package/README.md
CHANGED
|
@@ -1,86 +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
|
-
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
```
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
```
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
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
|
+
```
|
|
@@ -39,7 +39,10 @@ async function loadConfig(cwd) {
|
|
|
39
39
|
outputDir: rawConfig.outputDir ?? "src/api",
|
|
40
40
|
outputType: rawConfig.outputType ?? "ts",
|
|
41
41
|
typeName: rawConfig.typeName ?? "types",
|
|
42
|
-
cleanOutput: rawConfig.cleanOutput ?? false
|
|
42
|
+
cleanOutput: rawConfig.cleanOutput ?? false,
|
|
43
|
+
fileNaming: rawConfig.fileNaming ?? "path",
|
|
44
|
+
flattenQueryParam: rawConfig.flattenQueryParam ?? false,
|
|
45
|
+
mergeParams: rawConfig.mergeParams ?? false
|
|
43
46
|
};
|
|
44
47
|
return { configPath, config };
|
|
45
48
|
}
|
|
@@ -53,6 +56,152 @@ function groupByPrefix(operations) {
|
|
|
53
56
|
}, {});
|
|
54
57
|
}
|
|
55
58
|
|
|
59
|
+
// src/core/groupByController.ts
|
|
60
|
+
function isPathParam(segment) {
|
|
61
|
+
return segment.startsWith("{") && segment.endsWith("}");
|
|
62
|
+
}
|
|
63
|
+
function resultName(segments, childIndex) {
|
|
64
|
+
if (childIndex >= 2) {
|
|
65
|
+
return segments[childIndex - 1] + "_" + segments[childIndex];
|
|
66
|
+
}
|
|
67
|
+
return segments[childIndex];
|
|
68
|
+
}
|
|
69
|
+
function getPathMethods(operations) {
|
|
70
|
+
const map = /* @__PURE__ */ new Map();
|
|
71
|
+
for (const op of operations) {
|
|
72
|
+
if (!map.has(op.path)) map.set(op.path, /* @__PURE__ */ new Set());
|
|
73
|
+
map.get(op.path).add(op.method);
|
|
74
|
+
}
|
|
75
|
+
return map;
|
|
76
|
+
}
|
|
77
|
+
function deriveControllerName(path4, allPaths, pathMethods) {
|
|
78
|
+
const segments = path4.split("/").filter(Boolean);
|
|
79
|
+
if (segments.length === 0) return "index";
|
|
80
|
+
if (segments.length === 1) return segments[0];
|
|
81
|
+
let effectiveLen = segments.length;
|
|
82
|
+
while (effectiveLen > 1 && isPathParam(segments[effectiveLen - 1])) {
|
|
83
|
+
effectiveLen--;
|
|
84
|
+
}
|
|
85
|
+
if (effectiveLen === 1) return segments[0];
|
|
86
|
+
for (let depth = effectiveLen - 1; depth >= 0; depth--) {
|
|
87
|
+
const ancestorPath = "/" + segments.slice(0, depth).join("/");
|
|
88
|
+
const ancestorMethods = pathMethods.get(ancestorPath);
|
|
89
|
+
if (ancestorMethods && ancestorMethods.size > 1 && depth >= 1) {
|
|
90
|
+
return segments[depth - 1];
|
|
91
|
+
}
|
|
92
|
+
if (depth >= 1) {
|
|
93
|
+
if (allPaths.has(ancestorPath)) {
|
|
94
|
+
const ancestorController = deriveControllerName(ancestorPath, allPaths, pathMethods);
|
|
95
|
+
if (ancestorController !== segments[depth - 1]) {
|
|
96
|
+
return ancestorController;
|
|
97
|
+
}
|
|
98
|
+
return segments[depth - 1];
|
|
99
|
+
}
|
|
100
|
+
const hasDirectSibling = [...allPaths].some((p) => {
|
|
101
|
+
if (p === path4) return false;
|
|
102
|
+
if (!p.startsWith(ancestorPath + "/")) return false;
|
|
103
|
+
const remaining = p.slice(ancestorPath.length + 1);
|
|
104
|
+
return !remaining.includes("/");
|
|
105
|
+
});
|
|
106
|
+
if (hasDirectSibling) {
|
|
107
|
+
const siblingIsRest = [...allPaths].some((p) => {
|
|
108
|
+
if (p === path4) return false;
|
|
109
|
+
if (!p.startsWith(ancestorPath + "/")) return false;
|
|
110
|
+
const remaining = p.slice(ancestorPath.length + 1);
|
|
111
|
+
if (remaining.includes("/")) return false;
|
|
112
|
+
const methods = pathMethods.get(p);
|
|
113
|
+
return methods != null && methods.size > 1;
|
|
114
|
+
});
|
|
115
|
+
if (siblingIsRest) {
|
|
116
|
+
return resultName(segments, depth);
|
|
117
|
+
}
|
|
118
|
+
return segments[depth - 1];
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
const currentPath = "/" + segments.slice(0, depth + 1).join("/");
|
|
122
|
+
const hasChildren = [...allPaths].some(
|
|
123
|
+
(p) => p !== path4 && p.startsWith(currentPath + "/")
|
|
124
|
+
);
|
|
125
|
+
if (hasChildren) {
|
|
126
|
+
return resultName(segments, depth);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
for (let i = segments.length - 1; i >= 0; i--) {
|
|
130
|
+
if (!isPathParam(segments[i])) return resultName(segments, i);
|
|
131
|
+
}
|
|
132
|
+
return segments[segments.length - 1];
|
|
133
|
+
}
|
|
134
|
+
function groupByController(operations) {
|
|
135
|
+
const allPaths = new Set(operations.map((op) => op.path));
|
|
136
|
+
const pathMethods = getPathMethods(operations);
|
|
137
|
+
return operations.reduce((acc, operation) => {
|
|
138
|
+
const controllerName = deriveControllerName(operation.path, allPaths, pathMethods);
|
|
139
|
+
acc[controllerName] ??= [];
|
|
140
|
+
acc[controllerName].push(operation);
|
|
141
|
+
return acc;
|
|
142
|
+
}, {});
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// src/generators/schemaToTs.ts
|
|
146
|
+
function formatPropertyName(name) {
|
|
147
|
+
return /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(name) ? name : JSON.stringify(name);
|
|
148
|
+
}
|
|
149
|
+
function refToTypeName(ref) {
|
|
150
|
+
const parts = ref.split("/");
|
|
151
|
+
return parts[parts.length - 1] || "unknown";
|
|
152
|
+
}
|
|
153
|
+
function schemaToTs(schema) {
|
|
154
|
+
if (!schema) {
|
|
155
|
+
return "unknown";
|
|
156
|
+
}
|
|
157
|
+
if (schema.$ref) {
|
|
158
|
+
return refToTypeName(schema.$ref);
|
|
159
|
+
}
|
|
160
|
+
if (schema.enum?.length) {
|
|
161
|
+
return schema.enum.map((item) => JSON.stringify(item)).join(" | ");
|
|
162
|
+
}
|
|
163
|
+
if (schema.anyOf?.length) {
|
|
164
|
+
return schema.anyOf.map((item) => schemaToTs(item)).join(" | ");
|
|
165
|
+
}
|
|
166
|
+
if (schema.oneOf?.length) {
|
|
167
|
+
return schema.oneOf.map((item) => schemaToTs(item)).join(" | ");
|
|
168
|
+
}
|
|
169
|
+
if (schema.allOf?.length) {
|
|
170
|
+
return schema.allOf.map((item) => schemaToTs(item)).join(" & ");
|
|
171
|
+
}
|
|
172
|
+
if (schema.type === "array") {
|
|
173
|
+
return `${schemaToTs(schema.items)}[]`;
|
|
174
|
+
}
|
|
175
|
+
if (schema.type === "object" || schema.properties) {
|
|
176
|
+
const requiredSet = new Set(schema.required ?? []);
|
|
177
|
+
const properties = Object.entries(schema.properties ?? {}).map(([key, value]) => {
|
|
178
|
+
const optional = requiredSet.has(key) ? "" : "?";
|
|
179
|
+
return `${formatPropertyName(key)}${optional}: ${schemaToTs(value)};`;
|
|
180
|
+
});
|
|
181
|
+
if (!properties.length && schema.additionalProperties) {
|
|
182
|
+
const valueType = schema.additionalProperties === true ? "unknown" : schemaToTs(schema.additionalProperties);
|
|
183
|
+
return `{ [key: string]: ${valueType} }`;
|
|
184
|
+
}
|
|
185
|
+
return `{ ${properties.join(" ")} }`;
|
|
186
|
+
}
|
|
187
|
+
switch (schema.type) {
|
|
188
|
+
case "integer":
|
|
189
|
+
case "number":
|
|
190
|
+
return "number";
|
|
191
|
+
case "boolean":
|
|
192
|
+
return "boolean";
|
|
193
|
+
case "string":
|
|
194
|
+
return "string";
|
|
195
|
+
case "null":
|
|
196
|
+
return "null";
|
|
197
|
+
default:
|
|
198
|
+
return "unknown";
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
function toTypePropertyName(name) {
|
|
202
|
+
return formatPropertyName(name);
|
|
203
|
+
}
|
|
204
|
+
|
|
56
205
|
// src/utils/naming.ts
|
|
57
206
|
function toPascalCase(value) {
|
|
58
207
|
return value.split(/[^a-zA-Z0-9]+/).filter(Boolean).map((segment) => segment[0].toUpperCase() + segment.slice(1)).join("");
|
|
@@ -77,6 +226,36 @@ function buildTypeName(functionName, suffix) {
|
|
|
77
226
|
|
|
78
227
|
// src/core/parsePaths.ts
|
|
79
228
|
var HTTP_METHODS = ["get", "post", "put", "patch", "delete", "options", "head"];
|
|
229
|
+
function collectReferencedTypeNames(schema, collected = /* @__PURE__ */ new Set()) {
|
|
230
|
+
if (!schema) {
|
|
231
|
+
return [...collected];
|
|
232
|
+
}
|
|
233
|
+
if (schema.$ref) {
|
|
234
|
+
const typeName = schema.$ref.split("/").pop();
|
|
235
|
+
if (typeName) {
|
|
236
|
+
collected.add(typeName);
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
for (const child of schema.anyOf ?? []) {
|
|
240
|
+
collectReferencedTypeNames(child, collected);
|
|
241
|
+
}
|
|
242
|
+
for (const child of schema.oneOf ?? []) {
|
|
243
|
+
collectReferencedTypeNames(child, collected);
|
|
244
|
+
}
|
|
245
|
+
for (const child of schema.allOf ?? []) {
|
|
246
|
+
collectReferencedTypeNames(child, collected);
|
|
247
|
+
}
|
|
248
|
+
if (schema.items) {
|
|
249
|
+
collectReferencedTypeNames(schema.items, collected);
|
|
250
|
+
}
|
|
251
|
+
for (const property of Object.values(schema.properties ?? {})) {
|
|
252
|
+
collectReferencedTypeNames(property, collected);
|
|
253
|
+
}
|
|
254
|
+
if (schema.additionalProperties && schema.additionalProperties !== true) {
|
|
255
|
+
collectReferencedTypeNames(schema.additionalProperties, collected);
|
|
256
|
+
}
|
|
257
|
+
return [...collected];
|
|
258
|
+
}
|
|
80
259
|
function getPrimarySchema(content) {
|
|
81
260
|
if (!content) {
|
|
82
261
|
return void 0;
|
|
@@ -99,6 +278,9 @@ function parsePaths(document, docUrl, config) {
|
|
|
99
278
|
continue;
|
|
100
279
|
}
|
|
101
280
|
const moduleName = config.moduleName?.(docUrl) ?? "services";
|
|
281
|
+
if (config.ignoreUrl?.(apiPath, method, docUrl)) {
|
|
282
|
+
continue;
|
|
283
|
+
}
|
|
102
284
|
const functionName = sanitizeIdentifier(
|
|
103
285
|
config.renameMethod?.(apiPath, method) ?? buildDefaultMethodName(apiPath, method)
|
|
104
286
|
);
|
|
@@ -108,7 +290,43 @@ function parsePaths(document, docUrl, config) {
|
|
|
108
290
|
const requestBodySchema = getPrimarySchema(operation.requestBody?.content);
|
|
109
291
|
const responseSchema = getSuccessResponse(operation.responses);
|
|
110
292
|
const hasRequestParams = queryParams.length > 0 || pathParams.length > 0 || Boolean(requestBodySchema);
|
|
111
|
-
const
|
|
293
|
+
const bodyType = requestBodySchema ? schemaToTs(requestBodySchema) : void 0;
|
|
294
|
+
const requestImportTypes = collectReferencedTypeNames(requestBodySchema);
|
|
295
|
+
for (const param of queryParams) {
|
|
296
|
+
requestImportTypes.push(...collectReferencedTypeNames(param.schema));
|
|
297
|
+
}
|
|
298
|
+
for (const param of pathParams) {
|
|
299
|
+
requestImportTypes.push(...collectReferencedTypeNames(param.schema));
|
|
300
|
+
}
|
|
301
|
+
const pathFields = pathParams.map((param) => `${param.name}: ${schemaToTs(param.schema)};`);
|
|
302
|
+
const queryFields = queryParams.map((param) => `${param.name}${param.required ? "" : "?"}: ${schemaToTs(param.schema)};`);
|
|
303
|
+
const requestTypeParts = [];
|
|
304
|
+
if (bodyType) {
|
|
305
|
+
requestTypeParts.push(bodyType);
|
|
306
|
+
}
|
|
307
|
+
if (pathFields.length) {
|
|
308
|
+
if (config.mergeParams) {
|
|
309
|
+
requestTypeParts.push(`{ ${pathFields.join(" ")} }`);
|
|
310
|
+
} else {
|
|
311
|
+
requestTypeParts.push(`{ path: { ${pathFields.join(" ")} } }`);
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
if (queryFields.length) {
|
|
315
|
+
if (config.flattenQueryParam && queryParams.length === 1 && queryParams[0].schema?.$ref) {
|
|
316
|
+
if (config.mergeParams) {
|
|
317
|
+
requestTypeParts.push(schemaToTs(queryParams[0].schema));
|
|
318
|
+
} else {
|
|
319
|
+
requestTypeParts.push(`{ query: ${schemaToTs(queryParams[0].schema)} }`);
|
|
320
|
+
}
|
|
321
|
+
} else {
|
|
322
|
+
if (config.mergeParams) {
|
|
323
|
+
requestTypeParts.push(`{ ${queryFields.join(" ")} }`);
|
|
324
|
+
} else {
|
|
325
|
+
requestTypeParts.push(`{ query: { ${queryFields.join(" ")} } }`);
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
const requestTypeExpression = hasRequestParams ? requestTypeParts.join(" & ") || "unknown" : void 0;
|
|
112
330
|
operations.push({
|
|
113
331
|
docUrl,
|
|
114
332
|
moduleName,
|
|
@@ -123,7 +341,8 @@ function parsePaths(document, docUrl, config) {
|
|
|
123
341
|
pathParams,
|
|
124
342
|
requestBodySchema,
|
|
125
343
|
responseSchema,
|
|
126
|
-
|
|
344
|
+
requestTypeExpression,
|
|
345
|
+
requestImportTypes,
|
|
127
346
|
responseTypeName: responseSchema ? buildTypeName(functionName, "Response") : void 0,
|
|
128
347
|
fileBaseName: functionName
|
|
129
348
|
});
|
|
@@ -149,13 +368,14 @@ async function loadOpenApiDocument(docUrl) {
|
|
|
149
368
|
}
|
|
150
369
|
|
|
151
370
|
// src/generators/genIndex.ts
|
|
152
|
-
function generateIndexFile(operations,
|
|
153
|
-
const
|
|
154
|
-
|
|
155
|
-
|
|
371
|
+
function generateIndexFile(operations, _config) {
|
|
372
|
+
const exportSet = /* @__PURE__ */ new Set();
|
|
373
|
+
for (const operation of operations) {
|
|
374
|
+
exportSet.add(`export * from "./${operation.moduleName}/${operation.fileBaseName}";`);
|
|
375
|
+
}
|
|
156
376
|
return `/* eslint-disable */
|
|
157
377
|
// Auto-generated by swgto.
|
|
158
|
-
${
|
|
378
|
+
${[...exportSet].join("\n")}
|
|
159
379
|
`;
|
|
160
380
|
}
|
|
161
381
|
|
|
@@ -171,17 +391,43 @@ function buildFunctionDoc(operation, typeImportPath, requestParamType) {
|
|
|
171
391
|
lines.push(`@path ${operation.path}`);
|
|
172
392
|
lines.push(`@requestPath ${operation.requestPath}`);
|
|
173
393
|
lines.push(`@method ${operation.method.toUpperCase()}`);
|
|
174
|
-
lines.push("@template T");
|
|
175
394
|
lines.push(`@param ${requestParamType === "void" ? "[params]" : `{import(${JSON.stringify(typeImportPath)}).${requestParamType}} params`}`);
|
|
176
395
|
lines.push("@param [config]");
|
|
177
|
-
|
|
396
|
+
if (operation.responseTypeName) {
|
|
397
|
+
lines.push(`@returns {Promise<import(${JSON.stringify(typeImportPath)}).${operation.responseTypeName}>}`);
|
|
398
|
+
} else {
|
|
399
|
+
lines.push("@template T");
|
|
400
|
+
lines.push("@returns {Promise<T>}");
|
|
401
|
+
}
|
|
178
402
|
return ["/**", ...lines.map((line) => ` * ${line}`), " */"].join("\n");
|
|
179
403
|
}
|
|
180
|
-
function
|
|
181
|
-
const
|
|
182
|
-
|
|
183
|
-
const
|
|
184
|
-
const
|
|
404
|
+
function renderFunction(operation, useGenericUrl, mergeParams) {
|
|
405
|
+
const queryLine = operation.queryParams.length ? ` params: ${mergeParams ? "params" : "params?.query"},
|
|
406
|
+
` : "";
|
|
407
|
+
const bodyOnly = Boolean(operation.requestBodySchema) && !operation.queryParams.length && !operation.pathParams.length;
|
|
408
|
+
const bodyLine = operation.requestBodySchema ? ` data: ${mergeParams ? "params" : bodyOnly ? "params" : "params?.body"},
|
|
409
|
+
` : "";
|
|
410
|
+
const pathArg = mergeParams ? "params" : "params?.path";
|
|
411
|
+
const urlValue = useGenericUrl && operation.pathParams.length ? `buildUrl(${JSON.stringify(operation.requestPath)}, ${pathArg})` : operation.pathParams.length ? `buildUrl(${pathArg})` : JSON.stringify(operation.requestPath);
|
|
412
|
+
const pathLine = ` url: ${urlValue},
|
|
413
|
+
`;
|
|
414
|
+
return `export async function ${operation.functionName}(params, config) {
|
|
415
|
+
return request({
|
|
416
|
+
${pathLine} method: ${JSON.stringify(operation.method)},
|
|
417
|
+
${queryLine}${bodyLine} ...config,
|
|
418
|
+
});
|
|
419
|
+
}`;
|
|
420
|
+
}
|
|
421
|
+
function generateJsRequestFile(operation, httpClientPath, typeImportPath, mergeParams = false) {
|
|
422
|
+
const requestParamType = operation.requestTypeExpression ?? "void";
|
|
423
|
+
const queryLine = operation.queryParams.length ? ` params: ${mergeParams ? "params" : "params?.query"},
|
|
424
|
+
` : "";
|
|
425
|
+
const bodyOnly = Boolean(operation.requestBodySchema) && !operation.queryParams.length && !operation.pathParams.length;
|
|
426
|
+
const bodyLine = operation.requestBodySchema ? ` data: ${mergeParams ? "params" : bodyOnly ? "params" : "params?.body"},
|
|
427
|
+
` : "";
|
|
428
|
+
const pathArg = mergeParams ? "params" : "params?.path";
|
|
429
|
+
const pathLine = operation.pathParams.length ? ` url: buildUrl(${pathArg}),
|
|
430
|
+
` : ` url: ${JSON.stringify(operation.requestPath)},
|
|
185
431
|
`;
|
|
186
432
|
const buildUrlHelper = operation.pathParams.length ? `
|
|
187
433
|
function buildUrl(path) {
|
|
@@ -202,6 +448,26 @@ ${queryLine}${bodyLine} ...config,
|
|
|
202
448
|
}
|
|
203
449
|
`;
|
|
204
450
|
}
|
|
451
|
+
function generateJsModuleFile(operations, httpClientPath, typeImportPath, mergeParams = false) {
|
|
452
|
+
const needsBuildUrl = operations.some((op) => op.pathParams.length > 0);
|
|
453
|
+
const buildUrlHelper = needsBuildUrl ? `
|
|
454
|
+
function buildUrl(url, path) {
|
|
455
|
+
return url.replace(/\\{([^}]+)\\}/g, (_, key) => String(path?.[key] ?? ''));
|
|
456
|
+
}
|
|
457
|
+
` : "";
|
|
458
|
+
const functions = operations.map((op) => {
|
|
459
|
+
const doc = buildFunctionDoc(op, typeImportPath, op.requestTypeExpression ?? "void");
|
|
460
|
+
return `${doc}
|
|
461
|
+
|
|
462
|
+
${renderFunction(op, true, mergeParams)}`;
|
|
463
|
+
}).join("\n\n");
|
|
464
|
+
return `/* eslint-disable */
|
|
465
|
+
// Auto-generated by swgto.
|
|
466
|
+
import request from ${JSON.stringify(httpClientPath)};
|
|
467
|
+
${buildUrlHelper}
|
|
468
|
+
${functions}
|
|
469
|
+
`;
|
|
470
|
+
}
|
|
205
471
|
|
|
206
472
|
// src/generators/genRequestTs.ts
|
|
207
473
|
function buildFunctionDoc2(operation) {
|
|
@@ -217,20 +483,33 @@ function buildFunctionDoc2(operation) {
|
|
|
217
483
|
}
|
|
218
484
|
return ["/**", ...lines.map((line) => ` * ${line}`), " */", ""].join("\n");
|
|
219
485
|
}
|
|
220
|
-
function
|
|
486
|
+
function renderFunction2(operation, useGenericUrl, mergeParams) {
|
|
487
|
+
const requestArg = operation.requestTypeExpression ? `params: ${operation.requestTypeExpression}, config?: RequestConfig` : "params?: void, config?: RequestConfig";
|
|
488
|
+
const defaultResponseType = operation.responseTypeName ?? "unknown";
|
|
489
|
+
const bodyOnly = Boolean(operation.requestBodySchema) && !operation.queryParams.length && !operation.pathParams.length;
|
|
490
|
+
const bodyLine = operation.requestBodySchema ? ` data: ${mergeParams ? "params" : bodyOnly ? "params" : "params?.body"},
|
|
491
|
+
` : "";
|
|
492
|
+
const queryLine = operation.queryParams.length ? ` params: ${mergeParams ? "params" : "params?.query"},
|
|
493
|
+
` : "";
|
|
494
|
+
const pathArg = mergeParams ? "params" : "params?.path";
|
|
495
|
+
const urlValue = useGenericUrl && operation.pathParams.length ? `buildUrl(${JSON.stringify(operation.requestPath)}, ${pathArg})` : operation.pathParams.length ? `buildUrl(${pathArg})` : JSON.stringify(operation.requestPath);
|
|
496
|
+
const pathLine = ` url: ${urlValue},
|
|
497
|
+
`;
|
|
498
|
+
return `${buildFunctionDoc2(operation)}export async function ${operation.functionName}<T = ${defaultResponseType}>(${requestArg}): Promise<T> {
|
|
499
|
+
return request<T>({
|
|
500
|
+
${pathLine} method: ${JSON.stringify(operation.method)},
|
|
501
|
+
${queryLine}${bodyLine} ...config,
|
|
502
|
+
});
|
|
503
|
+
}`;
|
|
504
|
+
}
|
|
505
|
+
function generateTsRequestFile(operation, httpClientPath, typeImportPath, mergeParams = false) {
|
|
221
506
|
const importTypes = [
|
|
222
|
-
operation.
|
|
507
|
+
...operation.requestImportTypes,
|
|
223
508
|
operation.responseTypeName,
|
|
224
509
|
"RequestConfig"
|
|
225
510
|
].filter((value, index, array) => Boolean(value) && array.indexOf(value) === index);
|
|
226
511
|
const importLine = importTypes.length ? `import type { ${importTypes.join(", ")} } from ${JSON.stringify(typeImportPath)};
|
|
227
512
|
` : "";
|
|
228
|
-
const requestArg = operation.requestTypeName ? `params: ${operation.requestTypeName}, config?: RequestConfig` : "params?: void, config?: RequestConfig";
|
|
229
|
-
const defaultResponseType = operation.responseTypeName ?? "unknown";
|
|
230
|
-
const bodyLine = operation.requestBodySchema ? " data: params?.body,\n" : "";
|
|
231
|
-
const queryLine = operation.queryParams.length ? " params: params?.query,\n" : "";
|
|
232
|
-
const pathLine = operation.pathParams.length ? " url: buildUrl(params?.path),\n" : ` url: ${JSON.stringify(operation.requestPath)},
|
|
233
|
-
`;
|
|
234
513
|
const buildUrlHelper = operation.pathParams.length ? `
|
|
235
514
|
function buildUrl(path?: Record<string, unknown>): string {
|
|
236
515
|
return ${JSON.stringify(operation.requestPath)}.replace(/\\{([^}]+)\\}/g, (_, key) => String(path?.[key] ?? ''));
|
|
@@ -241,74 +520,31 @@ function buildUrl(path?: Record<string, unknown>): string {
|
|
|
241
520
|
import request from ${JSON.stringify(httpClientPath)};
|
|
242
521
|
${importLine}
|
|
243
522
|
${buildUrlHelper}
|
|
244
|
-
${
|
|
245
|
-
export async function ${operation.functionName}<T = ${defaultResponseType}>(${requestArg}): Promise<T> {
|
|
246
|
-
return request<T>({
|
|
247
|
-
${pathLine} method: ${JSON.stringify(operation.method)},
|
|
248
|
-
${queryLine}${bodyLine} ...config,
|
|
249
|
-
});
|
|
250
|
-
}
|
|
523
|
+
${renderFunction2(operation, false, mergeParams)}
|
|
251
524
|
`;
|
|
252
525
|
}
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
function refToTypeName(ref) {
|
|
259
|
-
const parts = ref.split("/");
|
|
260
|
-
return parts[parts.length - 1] || "unknown";
|
|
261
|
-
}
|
|
262
|
-
function schemaToTs(schema) {
|
|
263
|
-
if (!schema) {
|
|
264
|
-
return "unknown";
|
|
265
|
-
}
|
|
266
|
-
if (schema.$ref) {
|
|
267
|
-
return refToTypeName(schema.$ref);
|
|
268
|
-
}
|
|
269
|
-
if (schema.enum?.length) {
|
|
270
|
-
return schema.enum.map((item) => JSON.stringify(item)).join(" | ");
|
|
271
|
-
}
|
|
272
|
-
if (schema.anyOf?.length) {
|
|
273
|
-
return schema.anyOf.map((item) => schemaToTs(item)).join(" | ");
|
|
274
|
-
}
|
|
275
|
-
if (schema.oneOf?.length) {
|
|
276
|
-
return schema.oneOf.map((item) => schemaToTs(item)).join(" | ");
|
|
277
|
-
}
|
|
278
|
-
if (schema.allOf?.length) {
|
|
279
|
-
return schema.allOf.map((item) => schemaToTs(item)).join(" & ");
|
|
280
|
-
}
|
|
281
|
-
if (schema.type === "array") {
|
|
282
|
-
return `${schemaToTs(schema.items)}[]`;
|
|
283
|
-
}
|
|
284
|
-
if (schema.type === "object" || schema.properties) {
|
|
285
|
-
const requiredSet = new Set(schema.required ?? []);
|
|
286
|
-
const properties = Object.entries(schema.properties ?? {}).map(([key, value]) => {
|
|
287
|
-
const optional = requiredSet.has(key) ? "" : "?";
|
|
288
|
-
return `${formatPropertyName(key)}${optional}: ${schemaToTs(value)};`;
|
|
289
|
-
});
|
|
290
|
-
if (!properties.length && schema.additionalProperties) {
|
|
291
|
-
const valueType = schema.additionalProperties === true ? "unknown" : schemaToTs(schema.additionalProperties);
|
|
292
|
-
return `{ [key: string]: ${valueType} }`;
|
|
293
|
-
}
|
|
294
|
-
return `{ ${properties.join(" ")} }`;
|
|
295
|
-
}
|
|
296
|
-
switch (schema.type) {
|
|
297
|
-
case "integer":
|
|
298
|
-
case "number":
|
|
299
|
-
return "number";
|
|
300
|
-
case "boolean":
|
|
301
|
-
return "boolean";
|
|
302
|
-
case "string":
|
|
303
|
-
return "string";
|
|
304
|
-
case "null":
|
|
305
|
-
return "null";
|
|
306
|
-
default:
|
|
307
|
-
return "unknown";
|
|
526
|
+
function generateTsModuleFile(operations, httpClientPath, typeImportPath, mergeParams = false) {
|
|
527
|
+
const importTypeSet = /* @__PURE__ */ new Set();
|
|
528
|
+
for (const op of operations) {
|
|
529
|
+
for (const t of op.requestImportTypes) importTypeSet.add(t);
|
|
530
|
+
if (op.responseTypeName) importTypeSet.add(op.responseTypeName);
|
|
308
531
|
}
|
|
532
|
+
importTypeSet.add("RequestConfig");
|
|
533
|
+
const importLine = `import type { ${[...importTypeSet].join(", ")} } from ${JSON.stringify(typeImportPath)};
|
|
534
|
+
`;
|
|
535
|
+
const needsBuildUrl = operations.some((op) => op.pathParams.length > 0);
|
|
536
|
+
const buildUrlHelper = needsBuildUrl ? `
|
|
537
|
+
function buildUrl(url: string, path?: Record<string, unknown>): string {
|
|
538
|
+
return url.replace(/\\{([^}]+)\\}/g, (_, key) => String(path?.[key] ?? ''));
|
|
309
539
|
}
|
|
310
|
-
|
|
311
|
-
|
|
540
|
+
` : "";
|
|
541
|
+
const functions = operations.map((op) => renderFunction2(op, true, mergeParams)).join("\n\n");
|
|
542
|
+
return `/* eslint-disable */
|
|
543
|
+
// Auto-generated by swgto.
|
|
544
|
+
import request from ${JSON.stringify(httpClientPath)};
|
|
545
|
+
${importLine}${buildUrlHelper}
|
|
546
|
+
${functions}
|
|
547
|
+
`;
|
|
312
548
|
}
|
|
313
549
|
|
|
314
550
|
// src/generators/genTypes.ts
|
|
@@ -328,21 +564,17 @@ function buildSchemaDoc(schema) {
|
|
|
328
564
|
}
|
|
329
565
|
return formatDocLines(lines);
|
|
330
566
|
}
|
|
331
|
-
function
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
}
|
|
336
|
-
if (parameter.example !== void 0) {
|
|
337
|
-
lines.push(`@example ${JSON.stringify(parameter.example)}`);
|
|
338
|
-
}
|
|
339
|
-
return formatDocLines(lines);
|
|
567
|
+
function jsdocToInline(doc, indent) {
|
|
568
|
+
return doc.split("\n").filter((line) => {
|
|
569
|
+
const t = line.trim();
|
|
570
|
+
return t !== "/**" && t !== "*/" && !t.startsWith("/**");
|
|
571
|
+
}).map((line) => `${indent}// ${line.replace(/^\s*\* ?/, "")}`);
|
|
340
572
|
}
|
|
341
573
|
function renderObjectFields(fields, indent = "") {
|
|
342
574
|
const lines = [];
|
|
343
575
|
for (const field of fields) {
|
|
344
576
|
if (field.doc) {
|
|
345
|
-
lines.push(...field.doc
|
|
577
|
+
lines.push(...jsdocToInline(field.doc, indent));
|
|
346
578
|
}
|
|
347
579
|
lines.push(`${indent}${field.name}${field.optional ? "?" : ""}: ${field.type};`);
|
|
348
580
|
}
|
|
@@ -370,56 +602,6 @@ ${body}
|
|
|
370
602
|
}
|
|
371
603
|
function renderOperationTypes(operation) {
|
|
372
604
|
const blocks = [];
|
|
373
|
-
if (operation.requestTypeName) {
|
|
374
|
-
const fields = [];
|
|
375
|
-
const pathFields = [];
|
|
376
|
-
const queryFields = [];
|
|
377
|
-
for (const param of operation.pathParams) {
|
|
378
|
-
pathFields.push({
|
|
379
|
-
name: toTypePropertyName(param.name),
|
|
380
|
-
type: schemaToTs(param.schema),
|
|
381
|
-
optional: false,
|
|
382
|
-
doc: buildParameterDoc(param, `Path parameter: ${param.name}`)
|
|
383
|
-
});
|
|
384
|
-
}
|
|
385
|
-
for (const param of operation.queryParams) {
|
|
386
|
-
queryFields.push({
|
|
387
|
-
name: toTypePropertyName(param.name),
|
|
388
|
-
type: schemaToTs(param.schema),
|
|
389
|
-
optional: !param.required,
|
|
390
|
-
doc: buildParameterDoc(param, `Query parameter: ${param.name}`)
|
|
391
|
-
});
|
|
392
|
-
}
|
|
393
|
-
if (operation.requestBodySchema) {
|
|
394
|
-
const doc = buildSchemaDoc(operation.requestBodySchema);
|
|
395
|
-
if (doc) {
|
|
396
|
-
fields.push(...doc.split("\n"));
|
|
397
|
-
}
|
|
398
|
-
fields.push(`body${operation.requestBodySchema.nullable ? "?" : ""}: ${schemaToTs(operation.requestBodySchema)};`);
|
|
399
|
-
}
|
|
400
|
-
if (pathFields.length) {
|
|
401
|
-
fields.push("/** Path parameters */");
|
|
402
|
-
fields.push("path: {");
|
|
403
|
-
fields.push(...renderObjectFields(pathFields, " "));
|
|
404
|
-
fields.push("};");
|
|
405
|
-
}
|
|
406
|
-
if (queryFields.length) {
|
|
407
|
-
fields.push("/** Query parameters */");
|
|
408
|
-
fields.push("query: {");
|
|
409
|
-
fields.push(...renderObjectFields(queryFields, " "));
|
|
410
|
-
fields.push("};");
|
|
411
|
-
}
|
|
412
|
-
const requestDoc = formatDocLines(
|
|
413
|
-
[
|
|
414
|
-
operation.summary,
|
|
415
|
-
operation.description
|
|
416
|
-
].filter((value) => Boolean(value))
|
|
417
|
-
);
|
|
418
|
-
blocks.push(`${requestDoc ? `${requestDoc}
|
|
419
|
-
` : ""}export interface ${operation.requestTypeName} {
|
|
420
|
-
${fields.map((line) => ` ${line}`).join("\n")}
|
|
421
|
-
}`);
|
|
422
|
-
}
|
|
423
605
|
if (operation.responseTypeName) {
|
|
424
606
|
const responseDoc = buildSchemaDoc(operation.responseSchema);
|
|
425
607
|
blocks.push(`${responseDoc ? `${responseDoc}
|
|
@@ -433,7 +615,7 @@ function renderComponentSchemas(document) {
|
|
|
433
615
|
});
|
|
434
616
|
}
|
|
435
617
|
function toJSDocType(typeText) {
|
|
436
|
-
return typeText.replace(
|
|
618
|
+
return typeText.replace(/;\s*/g, ", ").replace(/,\s*\}/g, " }");
|
|
437
619
|
}
|
|
438
620
|
function generateTypesFile(documentMap, operations, config) {
|
|
439
621
|
if (config.outputType === "js") {
|
|
@@ -450,13 +632,6 @@ function generateTypesFile(documentMap, operations, config) {
|
|
|
450
632
|
}
|
|
451
633
|
}
|
|
452
634
|
for (const operation of operations) {
|
|
453
|
-
if (operation.requestTypeName) {
|
|
454
|
-
const rendered = renderOperationTypes(operation).find((line) => line.startsWith(`export interface ${operation.requestTypeName}`));
|
|
455
|
-
if (rendered) {
|
|
456
|
-
const body = rendered.replace(`export interface ${operation.requestTypeName} `, "").trim();
|
|
457
|
-
parts2.push(`/** @typedef ${body} ${operation.requestTypeName} */`);
|
|
458
|
-
}
|
|
459
|
-
}
|
|
460
635
|
if (operation.responseTypeName) {
|
|
461
636
|
parts2.push(`/** @typedef {${toJSDocType(schemaToTs(operation.responseSchema))}} ${operation.responseTypeName} */`);
|
|
462
637
|
}
|
|
@@ -481,16 +656,6 @@ function generateTypesFile(documentMap, operations, config) {
|
|
|
481
656
|
return `${parts.filter(Boolean).join("\n\n")}
|
|
482
657
|
`;
|
|
483
658
|
}
|
|
484
|
-
function generateApiDtsContent(operations, config) {
|
|
485
|
-
const lines = ["// Auto-generated by swgto.", 'export * from "./index";', `export * from "./${config.typeName}";`];
|
|
486
|
-
for (const operation of operations) {
|
|
487
|
-
const paramsType = operation.requestTypeName ?? "void";
|
|
488
|
-
const responseType = operation.responseTypeName ?? "unknown";
|
|
489
|
-
lines.push(`export declare function ${operation.functionName}<T = ${responseType}>(params${paramsType === "void" ? "?" : ""}: ${paramsType}, config?: import("./index").RequestConfig): Promise<T>;`);
|
|
490
|
-
}
|
|
491
|
-
return `${lines.join("\n")}
|
|
492
|
-
`;
|
|
493
|
-
}
|
|
494
659
|
|
|
495
660
|
// src/utils/fs.ts
|
|
496
661
|
import { mkdir, rm, writeFile } from "fs/promises";
|
|
@@ -503,12 +668,17 @@ async function writeTextFile(filePath, content) {
|
|
|
503
668
|
await writeFile(filePath, content, "utf8");
|
|
504
669
|
}
|
|
505
670
|
async function removeDir(dirPath) {
|
|
506
|
-
|
|
671
|
+
try {
|
|
672
|
+
await rm(dirPath, { recursive: true, force: true });
|
|
673
|
+
} catch {
|
|
674
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
675
|
+
await rm(dirPath, { recursive: true, force: true });
|
|
676
|
+
}
|
|
507
677
|
}
|
|
508
678
|
|
|
509
679
|
// src/generate.ts
|
|
510
|
-
function getRootImportPath(
|
|
511
|
-
return
|
|
680
|
+
function getRootImportPath(typeName) {
|
|
681
|
+
return `../${typeName}`;
|
|
512
682
|
}
|
|
513
683
|
async function generateFromConfig(cwd = process.cwd()) {
|
|
514
684
|
const { configPath, config } = await loadConfig(cwd);
|
|
@@ -516,6 +686,7 @@ async function generateFromConfig(cwd = process.cwd()) {
|
|
|
516
686
|
const operations = [];
|
|
517
687
|
if (config.cleanOutput) {
|
|
518
688
|
await removeDir(path3.join(cwd, config.outputDir));
|
|
689
|
+
console.log(`Cleaned output directory: ${config.outputDir}`);
|
|
519
690
|
}
|
|
520
691
|
for (const docUrl of config.docUrls) {
|
|
521
692
|
const document = await loadOpenApiDocument(docUrl);
|
|
@@ -525,33 +696,45 @@ async function generateFromConfig(cwd = process.cwd()) {
|
|
|
525
696
|
const grouped = groupByPrefix(operations);
|
|
526
697
|
const files = [];
|
|
527
698
|
for (const [moduleName, moduleOperations] of Object.entries(grouped)) {
|
|
528
|
-
|
|
529
|
-
const
|
|
530
|
-
const
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
699
|
+
if (config.fileNaming === "module") {
|
|
700
|
+
const controllerMap = groupByController(moduleOperations);
|
|
701
|
+
for (const [controllerName, controllerOperations] of Object.entries(controllerMap)) {
|
|
702
|
+
const relativeFile = path3.join(config.outputDir, moduleName, `${controllerName}.${config.outputType}`);
|
|
703
|
+
const absoluteFile = path3.join(cwd, relativeFile);
|
|
704
|
+
const content = config.outputType === "ts" ? generateTsModuleFile(controllerOperations, config.httpClientPath, getRootImportPath(config.typeName), config.mergeParams) : generateJsModuleFile(controllerOperations, config.httpClientPath, getRootImportPath(config.typeName), config.mergeParams);
|
|
705
|
+
for (const op of controllerOperations) {
|
|
706
|
+
op.fileBaseName = controllerName;
|
|
707
|
+
}
|
|
708
|
+
await writeTextFile(absoluteFile, content);
|
|
709
|
+
files.push(relativeFile);
|
|
710
|
+
}
|
|
711
|
+
} else {
|
|
712
|
+
for (const operation of moduleOperations) {
|
|
713
|
+
const relativeFile = path3.join(config.outputDir, moduleName, `${operation.fileBaseName}.${config.outputType}`);
|
|
714
|
+
const absoluteFile = path3.join(cwd, relativeFile);
|
|
715
|
+
const content = config.outputType === "ts" ? generateTsRequestFile(operation, config.httpClientPath, getRootImportPath(config.typeName), config.mergeParams) : generateJsRequestFile(operation, config.httpClientPath, getRootImportPath(config.typeName), config.mergeParams);
|
|
716
|
+
await writeTextFile(absoluteFile, content);
|
|
717
|
+
files.push(relativeFile);
|
|
718
|
+
}
|
|
534
719
|
}
|
|
535
720
|
}
|
|
536
721
|
const typesContent = generateTypesFile(documentMap, operations, config);
|
|
537
|
-
const apiDtsContent = generateApiDtsContent(operations, config);
|
|
538
722
|
const indexContent = generateIndexFile(operations, config);
|
|
539
723
|
const typesFile = path3.join(cwd, config.outputDir, `${config.typeName}.${config.outputType === "ts" ? "ts" : "js"}`);
|
|
540
|
-
const apiDtsFile = path3.join(cwd, config.outputDir, "api.d.ts");
|
|
541
724
|
const indexFile = path3.join(cwd, config.outputDir, `index.${config.outputType}`);
|
|
542
725
|
await writeTextFile(typesFile, typesContent);
|
|
543
|
-
await writeTextFile(apiDtsFile, apiDtsContent);
|
|
544
726
|
await writeTextFile(indexFile, indexContent);
|
|
545
727
|
files.push(
|
|
546
728
|
path3.relative(cwd, typesFile),
|
|
547
|
-
path3.relative(cwd, apiDtsFile),
|
|
548
729
|
path3.relative(cwd, indexFile)
|
|
549
730
|
);
|
|
550
731
|
return {
|
|
551
732
|
configPath,
|
|
552
733
|
files,
|
|
553
734
|
operationCount: operations.length,
|
|
554
|
-
moduleCount: Object.keys(grouped).length
|
|
735
|
+
moduleCount: Object.keys(grouped).length,
|
|
736
|
+
apiFileCount: files.length - 2
|
|
737
|
+
// exclude types + index
|
|
555
738
|
};
|
|
556
739
|
}
|
|
557
740
|
|
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-4OK6R2K5.js";
|
|
5
5
|
|
|
6
6
|
// src/cli.ts
|
|
7
7
|
async function main() {
|
|
@@ -10,8 +10,8 @@ async function main() {
|
|
|
10
10
|
const result = await generateFromConfig(process.cwd());
|
|
11
11
|
const elapsedMs = Date.now() - startedAt;
|
|
12
12
|
console.log(`swgto loaded config: ${result.configPath}`);
|
|
13
|
-
console.log(`Generated ${result.operationCount}
|
|
14
|
-
console.log(`Wrote ${result.files.length}
|
|
13
|
+
console.log(`Generated ${result.apiFileCount} api file(s), ${result.operationCount} operation(s) in ${result.moduleCount} module(s).`);
|
|
14
|
+
console.log(`Wrote ${result.files.length} file(s) total (including types, index).`);
|
|
15
15
|
console.log(`Done in ${elapsedMs}ms.`);
|
|
16
16
|
} catch (error) {
|
|
17
17
|
const message = error instanceof Error ? error.message : String(error);
|
package/dist/index.d.ts
CHANGED
|
@@ -3,6 +3,7 @@ interface GenerateResult {
|
|
|
3
3
|
files: string[];
|
|
4
4
|
operationCount: number;
|
|
5
5
|
moduleCount: number;
|
|
6
|
+
apiFileCount: number;
|
|
6
7
|
}
|
|
7
8
|
declare function generateFromConfig(cwd?: string): Promise<GenerateResult>;
|
|
8
9
|
|
|
@@ -13,11 +14,15 @@ interface SwaggerTsConfig {
|
|
|
13
14
|
httpClientPath: string;
|
|
14
15
|
renameMethod?: (path: string, method: string) => string;
|
|
15
16
|
resolveRequestPath?: (path: string, method: string, docUrl: string) => string;
|
|
17
|
+
ignoreUrl?: (path: string, method: string, docUrl: string) => boolean;
|
|
16
18
|
outputDir?: string;
|
|
17
19
|
moduleName?: (docUrl: string) => string;
|
|
18
20
|
outputType?: OutputType;
|
|
19
21
|
typeName?: string;
|
|
20
22
|
cleanOutput?: boolean;
|
|
23
|
+
fileNaming?: 'module' | 'path';
|
|
24
|
+
flattenQueryParam?: boolean;
|
|
25
|
+
mergeParams?: boolean;
|
|
21
26
|
}
|
|
22
27
|
|
|
23
28
|
export { type GenerateResult, type RequestConfig, type SwaggerTsConfig, generateFromConfig };
|
package/dist/index.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "swgto-ts",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "2.0.0",
|
|
4
4
|
"description": "Generate API request files from OpenAPI 3.x documents.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
"scripts": {
|
|
17
17
|
"build": "tsup src/index.ts src/cli.ts --format esm --dts --clean",
|
|
18
18
|
"dev": "tsup src/index.ts src/cli.ts --format esm --dts --watch",
|
|
19
|
-
"test": "
|
|
19
|
+
"test": "tsx ./src/cli.ts"
|
|
20
20
|
},
|
|
21
21
|
"keywords": [
|
|
22
22
|
"openapi",
|
|
@@ -29,7 +29,9 @@
|
|
|
29
29
|
"node": ">=18"
|
|
30
30
|
},
|
|
31
31
|
"dependencies": {
|
|
32
|
-
"jiti": "^2.4.2"
|
|
32
|
+
"jiti": "^2.4.2",
|
|
33
|
+
"ts-node": "^10.9.2",
|
|
34
|
+
"tsx": "^4.21.0"
|
|
33
35
|
},
|
|
34
36
|
"devDependencies": {
|
|
35
37
|
"@types/node": "^24.6.0",
|