czh-api 1.0.1 → 1.0.3
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/CHANGELOG.md +36 -0
- package/README.md +115 -1
- package/dist/commands/build.js +456 -42
- package/dist/commands/init.js +3 -2
- package/dist/core/parser.js +133 -23
- package/package.json +2 -1
- package/src/commands/build.ts +517 -54
- package/src/commands/init.ts +3 -2
- package/src/core/parser.ts +135 -21
package/dist/core/parser.js
CHANGED
|
@@ -13,22 +13,83 @@ function addSchemaWithDependencies(name, module, allSchemas) {
|
|
|
13
13
|
}
|
|
14
14
|
const schema = allSchemas[name];
|
|
15
15
|
if (!schema) {
|
|
16
|
+
console.warn(`警告: Schema "${name}" 未找到,跳过此引用`);
|
|
16
17
|
return; // Schema not found
|
|
17
18
|
}
|
|
18
19
|
module.schemas[name] = schema;
|
|
19
|
-
// Recursively add dependencies
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
20
|
+
// Recursively add dependencies with error handling
|
|
21
|
+
try {
|
|
22
|
+
JSON.stringify(schema, (key, value) => {
|
|
23
|
+
if (key === '$ref' && typeof value === 'string') {
|
|
24
|
+
const depName = getSchemaName(value);
|
|
25
|
+
if (depName && allSchemas[depName]) {
|
|
26
|
+
addSchemaWithDependencies(depName, module, allSchemas);
|
|
27
|
+
}
|
|
28
|
+
else {
|
|
29
|
+
console.warn(`警告: 依赖 Schema "${depName}" 未找到,跳过此引用`);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
return value;
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
catch (error) {
|
|
36
|
+
console.warn(`警告: 处理 Schema "${name}" 的依赖时出错:`, error);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
function toCamelCase(str) {
|
|
40
|
+
return str
|
|
41
|
+
.split('/')
|
|
42
|
+
.filter(p => p)
|
|
43
|
+
.map((part, index) => {
|
|
44
|
+
const cleaned = part.replace(/[^a-zA-Z0-9]/g, '');
|
|
45
|
+
if (index === 0) {
|
|
46
|
+
return cleaned.charAt(0).toLowerCase() + cleaned.slice(1);
|
|
24
47
|
}
|
|
25
|
-
return
|
|
26
|
-
})
|
|
48
|
+
return cleaned.charAt(0).toUpperCase() + cleaned.slice(1);
|
|
49
|
+
})
|
|
50
|
+
.join('');
|
|
27
51
|
}
|
|
28
|
-
function getModuleName(path) {
|
|
52
|
+
function getModuleName(path, pathPrefixes = []) {
|
|
53
|
+
// 尝试匹配配置的路径前缀
|
|
54
|
+
for (const prefix of pathPrefixes) {
|
|
55
|
+
if (path.startsWith(prefix.path)) {
|
|
56
|
+
// 获取前缀后的路径部分
|
|
57
|
+
const remainingPath = path.substring(prefix.path.length);
|
|
58
|
+
const parts = remainingPath.split('/').filter(p => p && !p.startsWith('{'));
|
|
59
|
+
// 确定包名
|
|
60
|
+
const packageName = prefix.packageName || toCamelCase(prefix.path);
|
|
61
|
+
// 如果有二级路径,使用二级路径作为子模块
|
|
62
|
+
if (parts.length > 0 && parts[0]) {
|
|
63
|
+
return `${packageName}/${parts[0]}`;
|
|
64
|
+
}
|
|
65
|
+
// 如果没有二级路径,直接使用包名
|
|
66
|
+
return packageName;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
// 如果没有匹配到配置的前缀,使用默认逻辑
|
|
29
70
|
const parts = path.split('/').filter(p => p && !p.startsWith('{'));
|
|
30
71
|
return parts[0] || 'default';
|
|
31
72
|
}
|
|
73
|
+
function convertOpenApiTypeToTypeScript(openApiType) {
|
|
74
|
+
if (!openApiType)
|
|
75
|
+
return 'any';
|
|
76
|
+
switch (openApiType) {
|
|
77
|
+
case 'integer':
|
|
78
|
+
return 'number';
|
|
79
|
+
case 'object':
|
|
80
|
+
return 'any';
|
|
81
|
+
case 'array':
|
|
82
|
+
return 'any[]';
|
|
83
|
+
case 'boolean':
|
|
84
|
+
return 'boolean';
|
|
85
|
+
case 'string':
|
|
86
|
+
return 'string';
|
|
87
|
+
case 'number':
|
|
88
|
+
return 'number';
|
|
89
|
+
default:
|
|
90
|
+
return 'any';
|
|
91
|
+
}
|
|
92
|
+
}
|
|
32
93
|
function extractJsdocParamsFromSchema(schema, allSchemas) {
|
|
33
94
|
var _a;
|
|
34
95
|
const params = [];
|
|
@@ -45,11 +106,58 @@ function extractJsdocParamsFromSchema(schema, allSchemas) {
|
|
|
45
106
|
if (targetSchema === null || targetSchema === void 0 ? void 0 : targetSchema.properties) {
|
|
46
107
|
for (const propName in targetSchema.properties) {
|
|
47
108
|
const prop = targetSchema.properties[propName];
|
|
48
|
-
|
|
49
|
-
//
|
|
109
|
+
let propType = 'any';
|
|
110
|
+
// 处理 anyOf 数组 (FastAPI 常用的联合类型)
|
|
111
|
+
if (prop.anyOf && Array.isArray(prop.anyOf)) {
|
|
112
|
+
const types = prop.anyOf.map(item => {
|
|
113
|
+
if (isReferenceObject(item)) {
|
|
114
|
+
return getSchemaName(item.$ref);
|
|
115
|
+
}
|
|
116
|
+
else {
|
|
117
|
+
const itemSchema = item;
|
|
118
|
+
// 处理 null 类型 (在 OpenAPI 中可能以不同方式表示)
|
|
119
|
+
if (itemSchema.type === 'null' ||
|
|
120
|
+
itemSchema.type === undefined && itemSchema.nullable === true ||
|
|
121
|
+
JSON.stringify(itemSchema) === '{"type":"null"}' ||
|
|
122
|
+
Object.keys(itemSchema).length === 0) {
|
|
123
|
+
return 'null';
|
|
124
|
+
}
|
|
125
|
+
return convertOpenApiTypeToTypeScript(itemSchema.type);
|
|
126
|
+
}
|
|
127
|
+
}).filter(type => type && type !== ''); // 只过滤掉空字符串,保留 any
|
|
128
|
+
if (types.length > 0) {
|
|
129
|
+
propType = types.join(' | ');
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
// 处理 oneOf 数组
|
|
133
|
+
else if (prop.oneOf && Array.isArray(prop.oneOf)) {
|
|
134
|
+
const types = prop.oneOf.map(item => {
|
|
135
|
+
if (isReferenceObject(item)) {
|
|
136
|
+
return getSchemaName(item.$ref);
|
|
137
|
+
}
|
|
138
|
+
else {
|
|
139
|
+
const itemSchema = item;
|
|
140
|
+
// 处理 null 类型 (在 OpenAPI 中可能以不同方式表示)
|
|
141
|
+
if (itemSchema.type === 'null' ||
|
|
142
|
+
itemSchema.type === undefined && itemSchema.nullable === true ||
|
|
143
|
+
JSON.stringify(itemSchema) === '{"type":"null"}' ||
|
|
144
|
+
Object.keys(itemSchema).length === 0) {
|
|
145
|
+
return 'null';
|
|
146
|
+
}
|
|
147
|
+
return convertOpenApiTypeToTypeScript(itemSchema.type);
|
|
148
|
+
}
|
|
149
|
+
}).filter(type => type && type !== ''); // 只过滤掉空字符串,保留 any
|
|
150
|
+
if (types.length > 0) {
|
|
151
|
+
propType = types.join(' | ');
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
// 处理普通类型
|
|
155
|
+
else if (prop.type) {
|
|
156
|
+
propType = convertOpenApiTypeToTypeScript(prop.type);
|
|
157
|
+
}
|
|
50
158
|
params.push({
|
|
51
159
|
name: propName,
|
|
52
|
-
type:
|
|
160
|
+
type: propType,
|
|
53
161
|
description: (prop === null || prop === void 0 ? void 0 : prop.description) || '',
|
|
54
162
|
required: (_a = targetSchema.required) === null || _a === void 0 ? void 0 : _a.includes(propName)
|
|
55
163
|
});
|
|
@@ -73,14 +181,22 @@ function getModuleNameFromPackage(operation) {
|
|
|
73
181
|
/**
|
|
74
182
|
* Processes the validated OpenAPI document into a structured format for code generation.
|
|
75
183
|
* @param api The bundled OpenAPI document.
|
|
184
|
+
* @param excludePaths Paths to exclude (by prefix).
|
|
185
|
+
* @param includePaths Paths to include (by prefix). If provided, only these paths will be processed.
|
|
186
|
+
* @param pathPrefixes Path prefix configurations for custom module grouping.
|
|
76
187
|
* @returns A structured representation of modules and their endpoints.
|
|
77
188
|
*/
|
|
78
|
-
const processApi = (api, excludePaths = []) => {
|
|
79
|
-
var _a, _b, _c, _d, _e, _f
|
|
189
|
+
const processApi = (api, excludePaths = [], includePaths = [], pathPrefixes = []) => {
|
|
190
|
+
var _a, _b, _c, _d, _e, _f;
|
|
80
191
|
const modules = {};
|
|
81
192
|
const allSchemas = ((_a = api.components) === null || _a === void 0 ? void 0 : _a.schemas) || api.definitions || {};
|
|
82
193
|
const moduleFunctionNames = {};
|
|
83
194
|
for (const path in api.paths) {
|
|
195
|
+
// 如果配置了 includePaths,只处理匹配的路径
|
|
196
|
+
if (includePaths.length > 0 && !includePaths.some(include => path.startsWith(include))) {
|
|
197
|
+
continue;
|
|
198
|
+
}
|
|
199
|
+
// 排除 excludePaths 中的路径
|
|
84
200
|
if (excludePaths.some(exclude => path.startsWith(exclude))) {
|
|
85
201
|
continue;
|
|
86
202
|
}
|
|
@@ -94,7 +210,7 @@ const processApi = (api, excludePaths = []) => {
|
|
|
94
210
|
if (!operation.tags)
|
|
95
211
|
continue;
|
|
96
212
|
const moduleNameFromPackage = getModuleNameFromPackage(operation);
|
|
97
|
-
const moduleName = moduleNameFromPackage || getModuleName(path);
|
|
213
|
+
const moduleName = moduleNameFromPackage || getModuleName(path, pathPrefixes);
|
|
98
214
|
if (!modules[moduleName]) {
|
|
99
215
|
modules[moduleName] = {
|
|
100
216
|
name: moduleName,
|
|
@@ -171,12 +287,6 @@ const processApi = (api, excludePaths = []) => {
|
|
|
171
287
|
};
|
|
172
288
|
for (const param of operation.parameters) {
|
|
173
289
|
if (!isReferenceObject(param)) {
|
|
174
|
-
const paramToAdd = {
|
|
175
|
-
name: param.name,
|
|
176
|
-
description: param.description || '',
|
|
177
|
-
required: param.required,
|
|
178
|
-
type: ((_c = param.schema) === null || _c === void 0 ? void 0 : _c.type) || 'any'
|
|
179
|
-
};
|
|
180
290
|
// If the parameter's schema is a reference, expand its properties
|
|
181
291
|
if (param.in === 'query' && param.schema && isReferenceObject(param.schema)) {
|
|
182
292
|
const schemaName = getSchemaName(param.schema.$ref);
|
|
@@ -233,7 +343,7 @@ const processApi = (api, excludePaths = []) => {
|
|
|
233
343
|
// Handle standard Request Body
|
|
234
344
|
if (operation.requestBody && !isReferenceObject(operation.requestBody) && !isFormData) {
|
|
235
345
|
const requestBody = operation.requestBody;
|
|
236
|
-
const jsonContent = (
|
|
346
|
+
const jsonContent = (_c = requestBody.content) === null || _c === void 0 ? void 0 : _c['application/json'];
|
|
237
347
|
if (jsonContent === null || jsonContent === void 0 ? void 0 : jsonContent.schema) {
|
|
238
348
|
if (isReferenceObject(jsonContent.schema)) {
|
|
239
349
|
const name = getSchemaName(jsonContent.schema.$ref);
|
|
@@ -269,7 +379,7 @@ const processApi = (api, excludePaths = []) => {
|
|
|
269
379
|
}
|
|
270
380
|
else if (isFormData && operation.requestBody && !isReferenceObject(operation.requestBody) && !requestBodyTypeName) {
|
|
271
381
|
// Handle cases where FormData is defined in requestBody but we haven't processed it yet
|
|
272
|
-
const formDataContent = (
|
|
382
|
+
const formDataContent = (_d = operation.requestBody.content) === null || _d === void 0 ? void 0 : _d['multipart/form-data'];
|
|
273
383
|
if (formDataContent === null || formDataContent === void 0 ? void 0 : formDataContent.schema) {
|
|
274
384
|
const typeName = `${functionName.charAt(0).toUpperCase() + functionName.slice(1)}Data`;
|
|
275
385
|
requestBodyTypeName = typeName;
|
|
@@ -282,7 +392,7 @@ const processApi = (api, excludePaths = []) => {
|
|
|
282
392
|
let responseTypeName = 'void';
|
|
283
393
|
const successResponse = operation.responses['200'];
|
|
284
394
|
if (successResponse) {
|
|
285
|
-
const mediaType = ((
|
|
395
|
+
const mediaType = ((_e = successResponse.content) === null || _e === void 0 ? void 0 : _e['*/*']) || ((_f = successResponse.content) === null || _f === void 0 ? void 0 : _f['application/json']);
|
|
286
396
|
const schema = mediaType === null || mediaType === void 0 ? void 0 : mediaType.schema;
|
|
287
397
|
if (schema && isReferenceObject(schema)) {
|
|
288
398
|
const name = getSchemaName(schema.$ref);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "czh-api",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.3",
|
|
4
4
|
"description": "A CLI tool to generate TypeScript API clients from Swagger/OpenAPI documents.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
"author": "Czh",
|
|
20
20
|
"license": "ISC",
|
|
21
21
|
"dependencies": {
|
|
22
|
+
"@apiture/openapi-down-convert": "^0.14.2",
|
|
22
23
|
"@types/pinyin": "^2.10.2",
|
|
23
24
|
"axios": "^1.10.0",
|
|
24
25
|
"chalk": "^4.1.2",
|