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.
@@ -16,6 +16,11 @@ export interface Endpoint {
16
16
  hasData: boolean;
17
17
  contentType?: string;
18
18
  jsdocParams: Array<{ name: string; type?: string; description?: string; required?: boolean }>;
19
+ // Separate jsdoc params for params and data to avoid merging
20
+ paramsJsdocParams: Array<{ name: string; type?: string; description?: string; required?: boolean }>;
21
+ dataJsdocParams: Array<{ name: string; type?: string; description?: string; required?: boolean }>;
22
+ // Jsdoc params for response type
23
+ responseJsdocParams: Array<{ name: string; type?: string; description?: string; required?: boolean }>;
19
24
  }
20
25
 
21
26
  export interface Module {
@@ -42,26 +47,91 @@ function addSchemaWithDependencies(name: string, module: Module, allSchemas: { [
42
47
 
43
48
  const schema = allSchemas[name];
44
49
  if (!schema) {
50
+ console.warn(`警告: Schema "${name}" 未找到,跳过此引用`);
45
51
  return; // Schema not found
46
52
  }
47
53
 
48
54
  module.schemas[name] = schema as OpenAPIV3.SchemaObject;
49
55
 
50
- // Recursively add dependencies
51
- JSON.stringify(schema, (key, value) => {
52
- if (key === '$ref' && typeof value === 'string') {
53
- const depName = getSchemaName(value);
54
- addSchemaWithDependencies(depName, module, allSchemas);
55
- }
56
- return value;
57
- });
56
+ // Recursively add dependencies with error handling
57
+ try {
58
+ JSON.stringify(schema, (key, value) => {
59
+ if (key === '$ref' && typeof value === 'string') {
60
+ const depName = getSchemaName(value);
61
+ if (depName && allSchemas[depName]) {
62
+ addSchemaWithDependencies(depName, module, allSchemas);
63
+ } else {
64
+ console.warn(`警告: 依赖 Schema "${depName}" 未找到,跳过此引用`);
65
+ }
66
+ }
67
+ return value;
68
+ });
69
+ } catch (error) {
70
+ console.warn(`警告: 处理 Schema "${name}" 的依赖时出错:`, error);
71
+ }
58
72
  }
59
73
 
60
- function getModuleName(path: string): string {
74
+ function toCamelCase(str: string): string {
75
+ return str
76
+ .split('/')
77
+ .filter(p => p)
78
+ .map((part, index) => {
79
+ const cleaned = part.replace(/[^a-zA-Z0-9]/g, '');
80
+ if (index === 0) {
81
+ return cleaned.charAt(0).toLowerCase() + cleaned.slice(1);
82
+ }
83
+ return cleaned.charAt(0).toUpperCase() + cleaned.slice(1);
84
+ })
85
+ .join('');
86
+ }
87
+
88
+ function getModuleName(path: string, pathPrefixes: Array<{ path: string; packageName?: string }> = []): string {
89
+ // 尝试匹配配置的路径前缀
90
+ for (const prefix of pathPrefixes) {
91
+ if (path.startsWith(prefix.path)) {
92
+ // 获取前缀后的路径部分
93
+ const remainingPath = path.substring(prefix.path.length);
94
+ const parts = remainingPath.split('/').filter(p => p && !p.startsWith('{'));
95
+
96
+ // 确定包名
97
+ const packageName = prefix.packageName || toCamelCase(prefix.path);
98
+
99
+ // 如果有二级路径,使用二级路径作为子模块
100
+ if (parts.length > 0 && parts[0]) {
101
+ return `${packageName}/${parts[0]}`;
102
+ }
103
+
104
+ // 如果没有二级路径,直接使用包名
105
+ return packageName;
106
+ }
107
+ }
108
+
109
+ // 如果没有匹配到配置的前缀,使用默认逻辑
61
110
  const parts = path.split('/').filter(p => p && !p.startsWith('{'));
62
111
  return parts[0] || 'default';
63
112
  }
64
113
 
114
+ function convertOpenApiTypeToTypeScript(openApiType: string | undefined): string {
115
+ if (!openApiType) return 'any';
116
+
117
+ switch (openApiType) {
118
+ case 'integer':
119
+ return 'number';
120
+ case 'object':
121
+ return 'any';
122
+ case 'array':
123
+ return 'any[]';
124
+ case 'boolean':
125
+ return 'boolean';
126
+ case 'string':
127
+ return 'string';
128
+ case 'number':
129
+ return 'number';
130
+ default:
131
+ return 'any';
132
+ }
133
+ }
134
+
65
135
  function extractJsdocParamsFromSchema(schema: OpenAPIV3.SchemaObject | OpenAPIV3.ReferenceObject, allSchemas: { [key: string]: any }): Required<Endpoint>['jsdocParams'] {
66
136
  const params: Required<Endpoint>['jsdocParams'] = [];
67
137
  if (!schema) return params;
@@ -78,12 +148,60 @@ function extractJsdocParamsFromSchema(schema: OpenAPIV3.SchemaObject | OpenAPIV3
78
148
  for (const propName in targetSchema.properties) {
79
149
  const prop = targetSchema.properties[propName] as OpenAPIV3.SchemaObject;
80
150
 
81
- // 调试: 打印出当前处理的属性
82
- // console.log(`正在处理属性: ${propName}`, '值为:', prop);
151
+ let propType = 'any';
152
+
153
+ // 处理 anyOf 数组 (FastAPI 常用的联合类型)
154
+ if (prop.anyOf && Array.isArray(prop.anyOf)) {
155
+ const types = prop.anyOf.map(item => {
156
+ if (isReferenceObject(item)) {
157
+ return getSchemaName(item.$ref);
158
+ } else {
159
+ const itemSchema = item as OpenAPIV3.SchemaObject;
160
+ // 处理 null 类型 (在 OpenAPI 中可能以不同方式表示)
161
+ if ((itemSchema as any).type === 'null' ||
162
+ itemSchema.type === undefined && (itemSchema as any).nullable === true ||
163
+ JSON.stringify(itemSchema) === '{"type":"null"}' ||
164
+ Object.keys(itemSchema).length === 0) {
165
+ return 'null';
166
+ }
167
+ return convertOpenApiTypeToTypeScript(itemSchema.type);
168
+ }
169
+ }).filter(type => type && type !== ''); // 只过滤掉空字符串,保留 any
170
+
171
+ if (types.length > 0) {
172
+ propType = types.join(' | ');
173
+ }
174
+ }
175
+ // 处理 oneOf 数组
176
+ else if (prop.oneOf && Array.isArray(prop.oneOf)) {
177
+ const types = prop.oneOf.map(item => {
178
+ if (isReferenceObject(item)) {
179
+ return getSchemaName(item.$ref);
180
+ } else {
181
+ const itemSchema = item as OpenAPIV3.SchemaObject;
182
+ // 处理 null 类型 (在 OpenAPI 中可能以不同方式表示)
183
+ if ((itemSchema as any).type === 'null' ||
184
+ itemSchema.type === undefined && (itemSchema as any).nullable === true ||
185
+ JSON.stringify(itemSchema) === '{"type":"null"}' ||
186
+ Object.keys(itemSchema).length === 0) {
187
+ return 'null';
188
+ }
189
+ return convertOpenApiTypeToTypeScript(itemSchema.type);
190
+ }
191
+ }).filter(type => type && type !== ''); // 只过滤掉空字符串,保留 any
192
+
193
+ if (types.length > 0) {
194
+ propType = types.join(' | ');
195
+ }
196
+ }
197
+ // 处理普通类型
198
+ else if (prop.type) {
199
+ propType = convertOpenApiTypeToTypeScript(prop.type);
200
+ }
83
201
 
84
202
  params.push({
85
203
  name: propName,
86
- type: prop?.type || 'any',
204
+ type: propType,
87
205
  description: prop?.description || '',
88
206
  required: targetSchema.required?.includes(propName)
89
207
  });
@@ -111,9 +229,10 @@ function getModuleNameFromPackage(operation: OpenAPIV3.OperationObject): string
111
229
  * @param api The bundled OpenAPI document.
112
230
  * @param excludePaths Paths to exclude (by prefix).
113
231
  * @param includePaths Paths to include (by prefix). If provided, only these paths will be processed.
232
+ * @param pathPrefixes Path prefix configurations for custom module grouping.
114
233
  * @returns A structured representation of modules and their endpoints.
115
234
  */
116
- export const processApi = (api: OpenAPI.Document, excludePaths: string[] = [], includePaths: string[] = []): Modules => {
235
+ export const processApi = (api: OpenAPI.Document, excludePaths: string[] = [], includePaths: string[] = [], pathPrefixes: Array<{ path: string; packageName?: string }> = []): Modules => {
117
236
  const modules: Modules = {};
118
237
  const allSchemas = (api as OpenAPIV3.Document).components?.schemas || (api as any).definitions ||{};
119
238
 
@@ -141,7 +260,7 @@ export const processApi = (api: OpenAPI.Document, excludePaths: string[] = [], i
141
260
  if (!operation.tags) continue;
142
261
 
143
262
  const moduleNameFromPackage = getModuleNameFromPackage(operation);
144
- const moduleName = moduleNameFromPackage || getModuleName(path);
263
+ const moduleName = moduleNameFromPackage || getModuleName(path, pathPrefixes);
145
264
 
146
265
  if (!modules[moduleName]) {
147
266
  modules[moduleName] = {
@@ -176,6 +295,9 @@ export const processApi = (api: OpenAPI.Document, excludePaths: string[] = [], i
176
295
  let requestBodyTypeName: string | undefined = undefined;
177
296
  let contentType: string | undefined = undefined;
178
297
  const jsdocParams: Required<Endpoint>['jsdocParams'] = [];
298
+ const paramsJsdocParams: Required<Endpoint>['paramsJsdocParams'] = [];
299
+ const dataJsdocParams: Required<Endpoint>['dataJsdocParams'] = [];
300
+ const responseJsdocParams: Required<Endpoint>['responseJsdocParams'] = [];
179
301
 
180
302
  // --- FormData Detection ---
181
303
  let isFormData = false;
@@ -227,13 +349,6 @@ export const processApi = (api: OpenAPI.Document, excludePaths: string[] = [], i
227
349
 
228
350
  for (const param of operation.parameters) {
229
351
  if (!isReferenceObject(param)) {
230
- const paramToAdd = {
231
- name: param.name,
232
- description: param.description || '',
233
- required: param.required,
234
- type: (param.schema as any)?.type || 'any'
235
- };
236
-
237
352
  // If the parameter's schema is a reference, expand its properties
238
353
  if (param.in === 'query' && param.schema && isReferenceObject(param.schema)) {
239
354
  const schemaName = getSchemaName(param.schema.$ref);
@@ -271,14 +386,18 @@ export const processApi = (api: OpenAPI.Document, excludePaths: string[] = [], i
271
386
  requestBodyTypeName = typeName;
272
387
  modules[moduleName].schemas[typeName] = formDataSchema;
273
388
  referencedTypes.push(typeName);
274
- jsdocParams.push(...extractJsdocParamsFromSchema(formDataSchema, allSchemas));
389
+ const formDataJsdoc = extractJsdocParamsFromSchema(formDataSchema, allSchemas);
390
+ jsdocParams.push(...formDataJsdoc);
391
+ dataJsdocParams.push(...formDataJsdoc);
275
392
  }
276
393
 
277
394
  if (Object.keys(paramsSchema.properties || {}).length > 0) {
278
395
  requestParamsTypeName = `${functionName.charAt(0).toUpperCase() + functionName.slice(1)}Params`;
279
396
  modules[moduleName].schemas[requestParamsTypeName] = paramsSchema;
280
397
  referencedTypes.push(requestParamsTypeName);
281
- jsdocParams.push(...extractJsdocParamsFromSchema(paramsSchema, allSchemas));
398
+ const paramsJsdoc = extractJsdocParamsFromSchema(paramsSchema, allSchemas);
399
+ jsdocParams.push(...paramsJsdoc);
400
+ paramsJsdocParams.push(...paramsJsdoc);
282
401
  }
283
402
  }
284
403
 
@@ -293,7 +412,9 @@ export const processApi = (api: OpenAPI.Document, excludePaths: string[] = [], i
293
412
  requestBodyTypeName = name;
294
413
  addSchemaWithDependencies(name, modules[moduleName], allSchemas);
295
414
  referencedTypes.push(name);
296
- jsdocParams.push(...extractJsdocParamsFromSchema(jsonContent.schema, allSchemas));
415
+ const bodyJsdoc = extractJsdocParamsFromSchema(jsonContent.schema, allSchemas);
416
+ jsdocParams.push(...bodyJsdoc);
417
+ dataJsdocParams.push(...bodyJsdoc);
297
418
  } else {
298
419
  // Handle inline schema (including arrays)
299
420
  const schema = jsonContent.schema as OpenAPIV3.SchemaObject;
@@ -309,10 +430,14 @@ export const processApi = (api: OpenAPI.Document, excludePaths: string[] = [], i
309
430
  const itemSchemaName = getSchemaName(schema.items.$ref);
310
431
  addSchemaWithDependencies(itemSchemaName, modules[moduleName], allSchemas);
311
432
  } else {
312
- jsdocParams.push(...extractJsdocParamsFromSchema(schema.items as OpenAPIV3.SchemaObject, allSchemas));
433
+ const arrayJsdoc = extractJsdocParamsFromSchema(schema.items as OpenAPIV3.SchemaObject, allSchemas);
434
+ jsdocParams.push(...arrayJsdoc);
435
+ dataJsdocParams.push(...arrayJsdoc);
313
436
  }
314
437
  } else if (schema.type === 'object') {
315
- jsdocParams.push(...extractJsdocParamsFromSchema(schema, allSchemas));
438
+ const objJsdoc = extractJsdocParamsFromSchema(schema, allSchemas);
439
+ jsdocParams.push(...objJsdoc);
440
+ dataJsdocParams.push(...objJsdoc);
316
441
  }
317
442
  }
318
443
  }
@@ -324,8 +449,9 @@ export const processApi = (api: OpenAPI.Document, excludePaths: string[] = [], i
324
449
  const typeName = `${functionName.charAt(0).toUpperCase() + functionName.slice(1)}Data`;
325
450
  requestBodyTypeName = typeName;
326
451
  modules[moduleName].schemas[typeName] = formDataContent.schema as OpenAPIV3.SchemaObject;
327
- referencedTypes.push(typeName);
328
- jsdocParams.push(...extractJsdocParamsFromSchema(formDataContent.schema, allSchemas));
452
+ referencedTypes.push(typeName);
453
+ const formJsdoc = extractJsdocParamsFromSchema(formDataContent.schema, allSchemas);
454
+ dataJsdocParams.push(...formJsdoc);
329
455
  }
330
456
  }
331
457
 
@@ -341,8 +467,34 @@ export const processApi = (api: OpenAPI.Document, excludePaths: string[] = [], i
341
467
  responseTypeName = name;
342
468
  addSchemaWithDependencies(name, modules[moduleName], allSchemas);
343
469
  referencedTypes.push(name);
344
- }
345
- }
470
+ responseJsdocParams.push(...extractJsdocParamsFromSchema(schema, allSchemas));
471
+ } else if (schema && !isReferenceObject(schema)) {
472
+ const inlineSchema = schema as OpenAPIV3.SchemaObject;
473
+ if (inlineSchema.type === 'object' || inlineSchema.properties) {
474
+ // Generate a named type for inline response schema
475
+ const typeName = `${functionName.charAt(0).toUpperCase() + functionName.slice(1)}Response`;
476
+ responseTypeName = typeName;
477
+ modules[moduleName].schemas[typeName] = inlineSchema;
478
+ referencedTypes.push(typeName);
479
+ responseJsdocParams.push(...extractJsdocParamsFromSchema(inlineSchema, allSchemas));
480
+ } else if (inlineSchema.type === 'array' && inlineSchema.items) {
481
+ if (isReferenceObject(inlineSchema.items)) {
482
+ const itemSchemaName = getSchemaName(inlineSchema.items.$ref);
483
+ // Prefer direct `ItemType[]` for array responses to avoid unnecessary alias schema compilation.
484
+ responseTypeName = `${itemSchemaName}[]`;
485
+ referencedTypes.push(itemSchemaName);
486
+ addSchemaWithDependencies(itemSchemaName, modules[moduleName], allSchemas);
487
+ responseJsdocParams.push(...extractJsdocParamsFromSchema(inlineSchema.items, allSchemas));
488
+ } else {
489
+ const typeName = `${functionName.charAt(0).toUpperCase() + functionName.slice(1)}Response`;
490
+ responseTypeName = typeName;
491
+ modules[moduleName].schemas[typeName] = inlineSchema;
492
+ referencedTypes.push(typeName);
493
+ responseJsdocParams.push(...extractJsdocParamsFromSchema(inlineSchema, allSchemas));
494
+ }
495
+ }
496
+ }
497
+ }
346
498
 
347
499
  // Convert path to template literal string for path parameters
348
500
  const urlTemplate = path.replace(/\{(\w+)\}/g, '${params.$1}');
@@ -360,6 +512,9 @@ export const processApi = (api: OpenAPI.Document, excludePaths: string[] = [], i
360
512
  hasData: !!requestBodyTypeName,
361
513
  contentType,
362
514
  jsdocParams,
515
+ paramsJsdocParams,
516
+ dataJsdocParams,
517
+ responseJsdocParams,
363
518
  };
364
519
 
365
520
  modules[moduleName].endpoints.push(endpoint);
@@ -367,4 +522,4 @@ export const processApi = (api: OpenAPI.Document, excludePaths: string[] = [], i
367
522
  }
368
523
 
369
524
  return modules;
370
- };
525
+ };
@@ -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}}