czh-api 1.0.2 → 1.0.4

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.
@@ -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
- JSON.stringify(schema, (key, value) => {
21
- if (key === '$ref' && typeof value === 'string') {
22
- const depName = getSchemaName(value);
23
- addSchemaWithDependencies(depName, module, allSchemas);
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 value;
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
- // console.log(`正在处理属性: ${propName}`, '值为:', prop);
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: (prop === null || prop === void 0 ? void 0 : prop.type) || 'any',
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
  });
@@ -75,10 +183,11 @@ function getModuleNameFromPackage(operation) {
75
183
  * @param api The bundled OpenAPI document.
76
184
  * @param excludePaths Paths to exclude (by prefix).
77
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.
78
187
  * @returns A structured representation of modules and their endpoints.
79
188
  */
80
- const processApi = (api, excludePaths = [], includePaths = []) => {
81
- var _a, _b, _c, _d, _e, _f, _g;
189
+ const processApi = (api, excludePaths = [], includePaths = [], pathPrefixes = []) => {
190
+ var _a, _b, _c, _d, _e, _f;
82
191
  const modules = {};
83
192
  const allSchemas = ((_a = api.components) === null || _a === void 0 ? void 0 : _a.schemas) || api.definitions || {};
84
193
  const moduleFunctionNames = {};
@@ -101,7 +210,7 @@ const processApi = (api, excludePaths = [], includePaths = []) => {
101
210
  if (!operation.tags)
102
211
  continue;
103
212
  const moduleNameFromPackage = getModuleNameFromPackage(operation);
104
- const moduleName = moduleNameFromPackage || getModuleName(path);
213
+ const moduleName = moduleNameFromPackage || getModuleName(path, pathPrefixes);
105
214
  if (!modules[moduleName]) {
106
215
  modules[moduleName] = {
107
216
  name: moduleName,
@@ -129,6 +238,9 @@ const processApi = (api, excludePaths = [], includePaths = []) => {
129
238
  let requestBodyTypeName = undefined;
130
239
  let contentType = undefined;
131
240
  const jsdocParams = [];
241
+ const paramsJsdocParams = [];
242
+ const dataJsdocParams = [];
243
+ const responseJsdocParams = [];
132
244
  // --- FormData Detection ---
133
245
  let isFormData = false;
134
246
  // Method 1: Check requestBody
@@ -178,12 +290,6 @@ const processApi = (api, excludePaths = [], includePaths = []) => {
178
290
  };
179
291
  for (const param of operation.parameters) {
180
292
  if (!isReferenceObject(param)) {
181
- const paramToAdd = {
182
- name: param.name,
183
- description: param.description || '',
184
- required: param.required,
185
- type: ((_c = param.schema) === null || _c === void 0 ? void 0 : _c.type) || 'any'
186
- };
187
293
  // If the parameter's schema is a reference, expand its properties
188
294
  if (param.in === 'query' && param.schema && isReferenceObject(param.schema)) {
189
295
  const schemaName = getSchemaName(param.schema.$ref);
@@ -228,26 +334,32 @@ const processApi = (api, excludePaths = [], includePaths = []) => {
228
334
  requestBodyTypeName = typeName;
229
335
  modules[moduleName].schemas[typeName] = formDataSchema;
230
336
  referencedTypes.push(typeName);
231
- jsdocParams.push(...extractJsdocParamsFromSchema(formDataSchema, allSchemas));
337
+ const formDataJsdoc = extractJsdocParamsFromSchema(formDataSchema, allSchemas);
338
+ jsdocParams.push(...formDataJsdoc);
339
+ dataJsdocParams.push(...formDataJsdoc);
232
340
  }
233
341
  if (Object.keys(paramsSchema.properties || {}).length > 0) {
234
342
  requestParamsTypeName = `${functionName.charAt(0).toUpperCase() + functionName.slice(1)}Params`;
235
343
  modules[moduleName].schemas[requestParamsTypeName] = paramsSchema;
236
344
  referencedTypes.push(requestParamsTypeName);
237
- jsdocParams.push(...extractJsdocParamsFromSchema(paramsSchema, allSchemas));
345
+ const paramsJsdoc = extractJsdocParamsFromSchema(paramsSchema, allSchemas);
346
+ jsdocParams.push(...paramsJsdoc);
347
+ paramsJsdocParams.push(...paramsJsdoc);
238
348
  }
239
349
  }
240
350
  // Handle standard Request Body
241
351
  if (operation.requestBody && !isReferenceObject(operation.requestBody) && !isFormData) {
242
352
  const requestBody = operation.requestBody;
243
- const jsonContent = (_d = requestBody.content) === null || _d === void 0 ? void 0 : _d['application/json'];
353
+ const jsonContent = (_c = requestBody.content) === null || _c === void 0 ? void 0 : _c['application/json'];
244
354
  if (jsonContent === null || jsonContent === void 0 ? void 0 : jsonContent.schema) {
245
355
  if (isReferenceObject(jsonContent.schema)) {
246
356
  const name = getSchemaName(jsonContent.schema.$ref);
247
357
  requestBodyTypeName = name;
248
358
  addSchemaWithDependencies(name, modules[moduleName], allSchemas);
249
359
  referencedTypes.push(name);
250
- jsdocParams.push(...extractJsdocParamsFromSchema(jsonContent.schema, allSchemas));
360
+ const bodyJsdoc = extractJsdocParamsFromSchema(jsonContent.schema, allSchemas);
361
+ jsdocParams.push(...bodyJsdoc);
362
+ dataJsdocParams.push(...bodyJsdoc);
251
363
  }
252
364
  else {
253
365
  // Handle inline schema (including arrays)
@@ -264,11 +376,15 @@ const processApi = (api, excludePaths = [], includePaths = []) => {
264
376
  addSchemaWithDependencies(itemSchemaName, modules[moduleName], allSchemas);
265
377
  }
266
378
  else {
267
- jsdocParams.push(...extractJsdocParamsFromSchema(schema.items, allSchemas));
379
+ const arrayJsdoc = extractJsdocParamsFromSchema(schema.items, allSchemas);
380
+ jsdocParams.push(...arrayJsdoc);
381
+ dataJsdocParams.push(...arrayJsdoc);
268
382
  }
269
383
  }
270
384
  else if (schema.type === 'object') {
271
- jsdocParams.push(...extractJsdocParamsFromSchema(schema, allSchemas));
385
+ const objJsdoc = extractJsdocParamsFromSchema(schema, allSchemas);
386
+ jsdocParams.push(...objJsdoc);
387
+ dataJsdocParams.push(...objJsdoc);
272
388
  }
273
389
  }
274
390
  }
@@ -276,26 +392,56 @@ const processApi = (api, excludePaths = [], includePaths = []) => {
276
392
  }
277
393
  else if (isFormData && operation.requestBody && !isReferenceObject(operation.requestBody) && !requestBodyTypeName) {
278
394
  // Handle cases where FormData is defined in requestBody but we haven't processed it yet
279
- const formDataContent = (_e = operation.requestBody.content) === null || _e === void 0 ? void 0 : _e['multipart/form-data'];
395
+ const formDataContent = (_d = operation.requestBody.content) === null || _d === void 0 ? void 0 : _d['multipart/form-data'];
280
396
  if (formDataContent === null || formDataContent === void 0 ? void 0 : formDataContent.schema) {
281
397
  const typeName = `${functionName.charAt(0).toUpperCase() + functionName.slice(1)}Data`;
282
398
  requestBodyTypeName = typeName;
283
399
  modules[moduleName].schemas[typeName] = formDataContent.schema;
284
400
  referencedTypes.push(typeName);
285
- jsdocParams.push(...extractJsdocParamsFromSchema(formDataContent.schema, allSchemas));
401
+ const formJsdoc = extractJsdocParamsFromSchema(formDataContent.schema, allSchemas);
402
+ dataJsdocParams.push(...formJsdoc);
286
403
  }
287
404
  }
288
405
  // Handle Response Body
289
406
  let responseTypeName = 'void';
290
407
  const successResponse = operation.responses['200'];
291
408
  if (successResponse) {
292
- const mediaType = ((_f = successResponse.content) === null || _f === void 0 ? void 0 : _f['*/*']) || ((_g = successResponse.content) === null || _g === void 0 ? void 0 : _g['application/json']);
409
+ const mediaType = ((_e = successResponse.content) === null || _e === void 0 ? void 0 : _e['*/*']) || ((_f = successResponse.content) === null || _f === void 0 ? void 0 : _f['application/json']);
293
410
  const schema = mediaType === null || mediaType === void 0 ? void 0 : mediaType.schema;
294
411
  if (schema && isReferenceObject(schema)) {
295
412
  const name = getSchemaName(schema.$ref);
296
413
  responseTypeName = name;
297
414
  addSchemaWithDependencies(name, modules[moduleName], allSchemas);
298
415
  referencedTypes.push(name);
416
+ responseJsdocParams.push(...extractJsdocParamsFromSchema(schema, allSchemas));
417
+ }
418
+ else if (schema && !isReferenceObject(schema)) {
419
+ const inlineSchema = schema;
420
+ if (inlineSchema.type === 'object' || inlineSchema.properties) {
421
+ // Generate a named type for inline response schema
422
+ const typeName = `${functionName.charAt(0).toUpperCase() + functionName.slice(1)}Response`;
423
+ responseTypeName = typeName;
424
+ modules[moduleName].schemas[typeName] = inlineSchema;
425
+ referencedTypes.push(typeName);
426
+ responseJsdocParams.push(...extractJsdocParamsFromSchema(inlineSchema, allSchemas));
427
+ }
428
+ else if (inlineSchema.type === 'array' && inlineSchema.items) {
429
+ if (isReferenceObject(inlineSchema.items)) {
430
+ const itemSchemaName = getSchemaName(inlineSchema.items.$ref);
431
+ // Prefer direct `ItemType[]` for array responses to avoid unnecessary alias schema compilation.
432
+ responseTypeName = `${itemSchemaName}[]`;
433
+ referencedTypes.push(itemSchemaName);
434
+ addSchemaWithDependencies(itemSchemaName, modules[moduleName], allSchemas);
435
+ responseJsdocParams.push(...extractJsdocParamsFromSchema(inlineSchema.items, allSchemas));
436
+ }
437
+ else {
438
+ const typeName = `${functionName.charAt(0).toUpperCase() + functionName.slice(1)}Response`;
439
+ responseTypeName = typeName;
440
+ modules[moduleName].schemas[typeName] = inlineSchema;
441
+ referencedTypes.push(typeName);
442
+ responseJsdocParams.push(...extractJsdocParamsFromSchema(inlineSchema, allSchemas));
443
+ }
444
+ }
299
445
  }
300
446
  }
301
447
  // Convert path to template literal string for path parameters
@@ -313,6 +459,9 @@ const processApi = (api, excludePaths = [], includePaths = []) => {
313
459
  hasData: !!requestBodyTypeName,
314
460
  contentType,
315
461
  jsdocParams,
462
+ paramsJsdocParams,
463
+ dataJsdocParams,
464
+ responseJsdocParams,
316
465
  };
317
466
  modules[moduleName].endpoints.push(endpoint);
318
467
  }
@@ -10,12 +10,7 @@ export const {{functionName}} = ({{#if hasParams}}params: {{requestParamsTypeNam
10
10
  return http.request<{{responseTypeName}}>({
11
11
  url: `{{path}}`,
12
12
  method: '{{method}}',
13
- {{#if hasParams}}
14
- params,
15
- {{/if}}
16
- {{#if hasData}}
17
- data,
18
- {{/if}}
13
+ {{#if hasParams}}{{#unless (eq method 'put')}}{{#unless (eq method 'post')}}params,{{/unless}}{{/unless}}{{/if}}{{#if hasData}}data,{{/if}}
19
14
  {{#if contentType}}
20
15
  headers: { 'Content-Type': '{{contentType}}' },
21
16
  {{/if}}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "czh-api",
3
- "version": "1.0.2",
3
+ "version": "1.0.4",
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",