czh-api 1.0.2 → 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.
@@ -3,7 +3,7 @@
3
3
  * @Author: czh
4
4
  * @Date: 2025-07-02 10:39:30
5
5
  * @LastEditors: Czh
6
- * @LastEditTime: 2025-12-25 12:40:34
6
+ * @LastEditTime: 2026-03-17 09:18:39
7
7
  */
8
8
  import chalk from 'chalk';
9
9
  import path from 'path';
@@ -15,6 +15,186 @@ import Handlebars from 'handlebars';
15
15
  import { rimraf } from 'rimraf';
16
16
  import axios from 'axios';
17
17
  import { compile } from 'json-schema-to-typescript';
18
+ import { Converter } from '@apiture/openapi-down-convert';
19
+
20
+ // 统一的类型转换函数
21
+ function convertOpenApiTypeToTypeScript(openApiType: string | undefined): string {
22
+ if (!openApiType) return 'any';
23
+
24
+ switch (openApiType) {
25
+ case 'integer':
26
+ return 'number';
27
+ case 'object':
28
+ return 'any';
29
+ case 'array':
30
+ return 'any[]';
31
+ case 'boolean':
32
+ return 'boolean';
33
+ case 'string':
34
+ return 'string';
35
+ case 'number':
36
+ return 'number';
37
+ default:
38
+ return 'any';
39
+ }
40
+ }
41
+
42
+ // 改进类型定义的辅助函数
43
+ function improveTypeDefinitions(typesContent: string, endpoints: any[]): string {
44
+ let improvedContent = typesContent;
45
+
46
+ // 收集所有需要改进的类型定义
47
+ const typeImprovements: { [typeName: string]: string } = {};
48
+
49
+ endpoints.forEach(endpoint => {
50
+ // 改进 params 类型
51
+ if (endpoint.requestParamsTypeName && endpoint.jsdocParams) {
52
+ const paramsFields = endpoint.jsdocParams
53
+ .filter((param: any) => param.name && param.type)
54
+ .map((param: any) => {
55
+ const optional = !param.required ? '?' : '';
56
+ let type = param.type;
57
+
58
+ // 处理联合类型,确保正确的 TypeScript 语法
59
+ if (type && type.includes(' | ')) {
60
+ // 如果包含 null,确保格式正确
61
+ if (type.includes('null')) {
62
+ type = type.replace(/\s*\|\s*null/g, ' | null');
63
+ }
64
+ } else {
65
+ // 处理基本类型转换
66
+ type = convertOpenApiTypeToTypeScript(type);
67
+ }
68
+
69
+ const comment = param.description ? ` // ${param.description}` : '';
70
+ return ` ${param.name}${optional}: ${type};${comment}`;
71
+ })
72
+ .join('\n');
73
+
74
+ if (paramsFields) {
75
+ typeImprovements[endpoint.requestParamsTypeName] = `export interface ${endpoint.requestParamsTypeName} {
76
+ ${paramsFields}
77
+ }`;
78
+ }
79
+ }
80
+
81
+ // 改进 requestBody 类型
82
+ if (endpoint.requestBodyTypeName && endpoint.jsdocParams) {
83
+ const dataFields = endpoint.jsdocParams
84
+ .filter((param: any) => param.name && param.type)
85
+ .map((param: any) => {
86
+ const optional = !param.required ? '?' : '';
87
+ let type = param.type;
88
+
89
+ // 处理联合类型,确保正确的 TypeScript 语法
90
+ if (type && type.includes(' | ')) {
91
+ // 如果包含 null,确保格式正确
92
+ if (type.includes('null')) {
93
+ type = type.replace(/\s*\|\s*null/g, ' | null');
94
+ }
95
+ } else {
96
+ // 处理基本类型转换
97
+ type = convertOpenApiTypeToTypeScript(type);
98
+ }
99
+
100
+ const comment = param.description ? ` // ${param.description}` : '';
101
+ return ` ${param.name}${optional}: ${type};${comment}`;
102
+ })
103
+ .join('\n');
104
+
105
+ if (dataFields) {
106
+ typeImprovements[endpoint.requestBodyTypeName] = `export interface ${endpoint.requestBodyTypeName} {
107
+ ${dataFields}
108
+ }`;
109
+ }
110
+ }
111
+ });
112
+
113
+ // 替换现有的类型定义
114
+ Object.entries(typeImprovements).forEach(([typeName, newDefinition]) => {
115
+ // 查找并替换现有的接口定义
116
+ const interfaceRegex = new RegExp(`export interface ${typeName}\\s*\\{[^}]*\\}`, 'g');
117
+ if (interfaceRegex.test(improvedContent)) {
118
+ improvedContent = improvedContent.replace(interfaceRegex, newDefinition);
119
+ } else {
120
+ // 如果没找到接口定义,添加到末尾
121
+ improvedContent += '\n\n' + newDefinition;
122
+ }
123
+ });
124
+
125
+ return improvedContent;
126
+ }
127
+
128
+ // 移除无用的类型别名
129
+ function removeUselessTypeAliases(typesContent: string): string {
130
+ let cleanedContent = typesContent;
131
+
132
+ // 收集要删除的类型别名及其对应的基础类型
133
+ const typeAliasMap: { [aliasName: string]: string } = {};
134
+
135
+ // 匹配简单的类型别名 (export type Name = string;)
136
+ const simpleTypeAliasRegex = /export type (\w+\d*) = (string|number|boolean)(\s*\|\s*null)?;?\n?/g;
137
+ let match;
138
+ while ((match = simpleTypeAliasRegex.exec(cleanedContent)) !== null) {
139
+ const aliasName = match[1];
140
+ const baseType = match[2] + (match[3] || '');
141
+ typeAliasMap[aliasName] = baseType;
142
+ }
143
+
144
+ // 匹配带注释的类型别名
145
+ const commentedTypeAliasRegex = /\/\*\*[^*]*\*\/\nexport type (\w+\d*) = (string|number|boolean)(\s*\|\s*null)?;?\n?/g;
146
+ while ((match = commentedTypeAliasRegex.exec(cleanedContent)) !== null) {
147
+ const aliasName = match[1];
148
+ const baseType = match[2] + (match[3] || '');
149
+ typeAliasMap[aliasName] = baseType;
150
+ }
151
+
152
+ // 匹配空的 JSON 类型别名 (export type Name = {};)
153
+ const jsonTypeAliasRegex = /export type (\w+) = \{\}(\s*\|\s*null)?;?\n?/g;
154
+ while ((match = jsonTypeAliasRegex.exec(cleanedContent)) !== null) {
155
+ const aliasName = match[1];
156
+ const baseType = 'any' + (match[2] || '');
157
+ typeAliasMap[aliasName] = baseType;
158
+ }
159
+
160
+ // 安全地替换接口中对这些类型别名的引用
161
+ Object.entries(typeAliasMap).forEach(([aliasName, baseType]) => {
162
+ // 创建更精确的正则表达式,确保只替换完整的类型引用
163
+ const escapedAliasName = aliasName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
164
+
165
+ // 替换接口字段中的类型引用 (fieldName: AliasName;)
166
+ const fieldTypeRegex = new RegExp(`(\\w+\\??:\\s*)${escapedAliasName}(\\s*;)`, 'g');
167
+ cleanedContent = cleanedContent.replace(fieldTypeRegex, `$1${baseType}$2`);
168
+
169
+ // 替换接口字段中的类型引用,包括注释 (fieldName: AliasName; // comment)
170
+ const fieldTypeWithCommentRegex = new RegExp(`(\\w+\\??:\\s*)${escapedAliasName}(\\s*;\\s*//[^\\n]*)`, 'g');
171
+ cleanedContent = cleanedContent.replace(fieldTypeWithCommentRegex, `$1${baseType}$2`);
172
+ });
173
+
174
+ // 移除类型别名定义(按行移除,避免破坏其他内容)
175
+ const lines = cleanedContent.split('\n');
176
+ const filteredLines = lines.filter(line => {
177
+ // 移除简单类型别名
178
+ if (/^export type \w+\d* = (string|number|boolean)(\s*\|\s*null)?;?\s*$/.test(line)) {
179
+ return false;
180
+ }
181
+ // 移除空对象类型别名
182
+ if (/^export type \w+ = \{\}(\s*\|\s*null)?;?\s*$/.test(line)) {
183
+ return false;
184
+ }
185
+ return true;
186
+ });
187
+
188
+ cleanedContent = filteredLines.join('\n');
189
+
190
+ // 清理多余的空行和孤立的注释
191
+ cleanedContent = cleanedContent.replace(/\n{3,}/g, '\n\n');
192
+
193
+ // 移除孤立的类型注释(没有对应类型定义的注释)
194
+ cleanedContent = cleanedContent.replace(/\/\*\*\n \* [^\n]*\n \*\/\n(?!export)/g, '');
195
+
196
+ return cleanedContent;
197
+ }
18
198
 
19
199
  interface IConfig {
20
200
  url: string;
@@ -24,6 +204,7 @@ interface IConfig {
24
204
  customImports?: string[];
25
205
  excludePaths?: string[];
26
206
  includePaths?: string[];
207
+ pathPrefixes?: Array<{ path: string; packageName?: string }>;
27
208
  }
28
209
 
29
210
  export const handleBuild = async () => {
@@ -41,11 +222,58 @@ export const handleBuild = async () => {
41
222
  // Fetch and parse Swagger/OpenAPI document
42
223
  console.log(chalk.blue(`正在从 ${config.url} 获取 API 文档...`));
43
224
  const response = await axios.get(config.url);
44
- const api = await SwaggerParser.bundle(response.data);
45
- console.log(chalk.green('Swagger document fetched and bundled successfully.'));
225
+ let apiDoc = response.data;
226
+ let isConverted = false;
227
+
228
+ // 检查是否为 OpenAPI 3.1.0,如果是则转换为 3.0.x
229
+ if (apiDoc.openapi && apiDoc.openapi.startsWith('3.1')) {
230
+ console.log(chalk.yellow('检测到 OpenAPI 3.1.0 规范,正在转换为 3.0.x...'));
231
+ try {
232
+ // 使用更保守的转换选项
233
+ const converter = new Converter(apiDoc, {
234
+ verbose: false,
235
+ allOfTransform: false, // 不使用 allOf 转换,避免引用问题
236
+ deleteExampleWithId: false,
237
+ convertSchemaComments: false
238
+ });
239
+ apiDoc = converter.convert();
240
+ isConverted = true;
241
+ console.log(chalk.green('OpenAPI 3.1.0 已成功转换为 3.0.x'));
242
+ } catch (convertError: any) {
243
+ console.warn(chalk.yellow('OpenAPI 版本转换失败,尝试直接解析原始文档...'));
244
+ console.warn(chalk.yellow('转换错误:'), convertError?.message || convertError);
245
+ // 如果转换失败,尝试直接使用原始文档
246
+ apiDoc = response.data;
247
+ isConverted = false;
248
+ }
249
+ }
250
+
251
+ let api;
252
+ try {
253
+ api = await SwaggerParser.bundle(apiDoc);
254
+ console.log(chalk.green('Swagger document fetched and bundled successfully.'));
255
+ } catch (bundleError: any) {
256
+ console.warn(chalk.yellow('SwaggerParser.bundle 失败,尝试直接使用文档...'));
257
+ console.warn(chalk.yellow('Bundle 错误:'), bundleError?.message || bundleError);
258
+
259
+ // 如果 bundle 失败,直接使用转换后的文档
260
+ if (isConverted) {
261
+ console.log(chalk.blue('使用转换后的文档继续处理...'));
262
+ api = apiDoc;
263
+ } else {
264
+ // 如果是 OpenAPI 3.1.0 且之前转换失败,提供更详细的错误信息
265
+ if (apiDoc.openapi && apiDoc.openapi.startsWith('3.1')) {
266
+ console.error(chalk.red('建议解决方案:'));
267
+ console.error(chalk.red('1. 检查 FastAPI 应用是否可以生成 OpenAPI 3.0.x 版本的文档'));
268
+ console.error(chalk.red('2. 或者在 FastAPI 中配置: app = FastAPI(openapi_version="3.0.2")'));
269
+ console.error(chalk.red('3. 或者使用 excludePaths 排除有问题的接口路径'));
270
+ }
271
+ throw bundleError;
272
+ }
273
+ }
46
274
 
47
275
  // Process the API document
48
- const modules = processApi(api, config.excludePaths || [], config.includePaths || []);
276
+ const modules = processApi(api, config.excludePaths || [], config.includePaths || [], config.pathPrefixes || []);
49
277
  console.log(chalk.blue('API processed into modules.'));
50
278
 
51
279
  // Clean output directory
@@ -78,70 +306,304 @@ export const handleBuild = async () => {
78
306
  const module = modules[moduleName];
79
307
  const moduleDir = path.join(outputDir, moduleName);
80
308
  fs.mkdirSync(moduleDir, { recursive: true });
309
+
310
+ // 提取模块的基础名称(用于文件名)
311
+ const moduleBaseName = moduleName.includes('/')
312
+ ? moduleName.split('/').pop()
313
+ : moduleName;
81
314
 
82
315
  let typesContent = '';
83
316
  // Generate types.ts
84
317
  if (Object.keys(module.schemas).length > 0) {
85
- const schemasWithTitles: { [key: string]: OpenAPIV3.SchemaObject } = {};
86
- for (const schemaName in module.schemas) {
87
- schemasWithTitles[schemaName] = {
88
- ...module.schemas[schemaName],
89
- title: schemaName,
318
+ console.log(chalk.blue(`正在为模块 "${moduleName}" 生成类型文件,包含 ${Object.keys(module.schemas).length} schema`));
319
+ try {
320
+ const schemasWithTitles: { [key: string]: OpenAPIV3.SchemaObject } = {};
321
+ for (const schemaName in module.schemas) {
322
+ schemasWithTitles[schemaName] = {
323
+ ...module.schemas[schemaName],
324
+ title: schemaName,
325
+ };
326
+ }
327
+
328
+ const rootSchemaForCompiler = {
329
+ title: 'schemas',
330
+ type: 'object',
331
+ properties: {},
332
+ additionalProperties: false,
333
+ definitions: schemasWithTitles,
334
+ components: {
335
+ schemas: schemasWithTitles
336
+ }
90
337
  };
91
- }
92
-
93
- const rootSchemaForCompiler = {
94
- title: 'schemas',
95
- type: 'object',
96
- properties: {},
97
- additionalProperties: false,
98
- definitions: schemasWithTitles,
99
- components: {
100
- schemas: schemasWithTitles
338
+
339
+ console.log(chalk.blue(`开始编译 ${Object.keys(schemasWithTitles).length} 个类型定义...`));
340
+ typesContent = await compile(rootSchemaForCompiler as any, 'schemas', {
341
+ bannerComment: '/* eslint-disable */\n/**\n* This file was automatically generated by czh-api.\n* DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file,\n* and run czh-api build to regenerate this file.\n*/',
342
+ unreachableDefinitions: true,
343
+ additionalProperties: false,
344
+ });
345
+
346
+ typesContent = typesContent.replace(/export interface \w+ \{\s*\}\n/g, '');
347
+ const refCommentRegex = /\/\*\*\n \* This interface was referenced by `\w+`'s JSON-Schema[\s\S]*?\*\/\n/g;
348
+ typesContent = typesContent.replace(refCommentRegex, '');
349
+ typesContent = typesContent.replace(/:\s*\{\}\[\]/g, ': any[]');
350
+ typesContent = typesContent.replace(/\[k: string\]: \{\}/g, '[k: string]: any');
351
+ const inlineIndexSignatureRegex = /:\s*\{\s*\/\*\*[\s\S]*?\*\/[\s\n\r]*\[k: string\]: any;\s*\};/g;
352
+ typesContent = typesContent.replace(inlineIndexSignatureRegex, ': any;');
353
+ typesContent = typesContent.replace(/: \{\}/g, ': any');
354
+ typesContent = typesContent.replace(/\}\n/g, '}\n\n');
355
+
356
+ // 移除无用的类型别名
357
+ typesContent = removeUselessTypeAliases(typesContent);
358
+
359
+ // 改进类型定义:用 JSDoc 信息补充接口定义
360
+ const improvedTypes = improveTypeDefinitions(typesContent, module.endpoints);
361
+ if (improvedTypes !== typesContent) {
362
+ typesContent = improvedTypes;
363
+ console.log(chalk.blue(`✓ 使用 JSDoc 信息改进了类型定义`));
101
364
  }
102
- };
103
-
104
- typesContent = await compile(rootSchemaForCompiler as any, 'schemas', {
105
- bannerComment: '/* eslint-disable */\n/**\n* This file was automatically generated by czh-api.\n* DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file,\n* and run czh-api build to regenerate this file.\n*/',
106
- unreachableDefinitions: true,
107
- additionalProperties: false,
108
- });
109
-
110
- typesContent = typesContent.replace(/export interface \w+ \{\s*\}\n/g, '');
111
- const refCommentRegex = /\/\*\*\n \* This interface was referenced by `\w+`'s JSON-Schema[\s\S]*?\*\/\n/g;
112
- typesContent = typesContent.replace(refCommentRegex, '');
113
- typesContent = typesContent.replace(/:\s*\{\}\[\]/g, ': any[]');
114
- typesContent = typesContent.replace(/\[k: string\]: \{\}/g, '[k: string]: any');
115
- const inlineIndexSignatureRegex = /:\s*\{\s*\/\*\*[\s\S]*?\*\/[\s\n\r]*\[k: string\]: any;\s*\};/g;
116
- typesContent = typesContent.replace(inlineIndexSignatureRegex, ': any;');
117
- typesContent = typesContent.replace(/: \{\}/g, ': any');
118
- typesContent = typesContent.replace(/\}\n/g, '}\n\n');
119
-
120
- fs.writeFileSync(path.join(moduleDir, 'types.ts'), typesContent);
365
+
366
+ fs.writeFileSync(path.join(moduleDir, 'types.ts'), typesContent);
367
+ console.log(chalk.green(`✓ 模块 "${moduleName}" 类型文件生成成功`));
368
+ } catch (typeError: any) {
369
+ // 检查是否是引用错误
370
+ const errorMessage = typeError?.message || typeError;
371
+ if (errorMessage.includes('Missing $ref pointer')) {
372
+ console.warn(chalk.yellow(`警告: 模块 "${moduleName}" 存在 OpenAPI 引用错误,尝试过滤有问题的 schema...`));
373
+ console.warn(chalk.yellow('引用错误:'), errorMessage);
374
+
375
+ // 尝试逐个验证 schema,过滤掉有问题的
376
+ const validSchemas: { [key: string]: OpenAPIV3.SchemaObject } = {};
377
+ const invalidSchemas: string[] = [];
378
+
379
+ for (const schemaName in module.schemas) {
380
+ try {
381
+ // 尝试单独编译每个 schema
382
+ const testSchema = {
383
+ title: 'test',
384
+ type: 'object',
385
+ properties: {},
386
+ additionalProperties: false,
387
+ definitions: { [schemaName]: module.schemas[schemaName] },
388
+ components: { schemas: { [schemaName]: module.schemas[schemaName] } }
389
+ };
390
+ await compile(testSchema as any, 'test', { unreachableDefinitions: true });
391
+ validSchemas[schemaName] = {
392
+ ...module.schemas[schemaName],
393
+ title: schemaName
394
+ };
395
+ } catch (schemaError) {
396
+ invalidSchemas.push(schemaName);
397
+ }
398
+ }
399
+
400
+ if (Object.keys(validSchemas).length > 0) {
401
+ console.log(chalk.blue(`找到 ${Object.keys(validSchemas).length} 个有效 schema,${invalidSchemas.length} 个无效 schema`));
402
+ if (invalidSchemas.length > 0) {
403
+ console.warn(chalk.yellow('无效的 schema:'), invalidSchemas.join(', '));
404
+ }
405
+
406
+ try {
407
+ const rootSchemaForCompiler = {
408
+ title: 'schemas',
409
+ type: 'object',
410
+ properties: {},
411
+ additionalProperties: false,
412
+ definitions: validSchemas,
413
+ components: { schemas: validSchemas }
414
+ };
415
+
416
+ typesContent = await compile(rootSchemaForCompiler as any, 'schemas', {
417
+ bannerComment: '/* eslint-disable */\n/**\n* This file was automatically generated by czh-api.\n* DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file,\n* and run czh-api build to regenerate this file.\n*/',
418
+ unreachableDefinitions: true,
419
+ additionalProperties: false,
420
+ });
421
+
422
+ // 应用相同的后处理
423
+ typesContent = typesContent.replace(/export interface \w+ \{\s*\}\n/g, '');
424
+ const refCommentRegex = /\/\*\*\n \* This interface was referenced by `\w+`'s JSON-Schema[\s\S]*?\*\/\n/g;
425
+ typesContent = typesContent.replace(refCommentRegex, '');
426
+ typesContent = typesContent.replace(/:\s*\{\}\[\]/g, ': any[]');
427
+ typesContent = typesContent.replace(/\[k: string\]: \{\}/g, '[k: string]: any');
428
+ const inlineIndexSignatureRegex = /:\s*\{\s*\/\*\*[\s\S]*?\*\/[\s\n\r]*\[k: string\]: any;\s*\};/g;
429
+ typesContent = typesContent.replace(inlineIndexSignatureRegex, ': any;');
430
+ typesContent = typesContent.replace(/: \{\}/g, ': any');
431
+ typesContent = typesContent.replace(/\}\n/g, '}\n\n');
432
+
433
+ typesContent = removeUselessTypeAliases(typesContent);
434
+
435
+ const improvedTypes = improveTypeDefinitions(typesContent, module.endpoints);
436
+ if (improvedTypes !== typesContent) {
437
+ typesContent = improvedTypes;
438
+ console.log(chalk.blue(`✓ 使用 JSDoc 信息改进了类型定义`));
439
+ }
440
+
441
+ fs.writeFileSync(path.join(moduleDir, 'types.ts'), typesContent);
442
+ console.log(chalk.green(`✓ 模块 "${moduleName}" 部分类型文件生成成功 (${Object.keys(validSchemas).length}/${Object.keys(module.schemas).length} 个 schema)`));
443
+ } catch (partialError) {
444
+ console.warn(chalk.yellow(`部分 schema 编译也失败,回退到 JSDoc 类型生成`));
445
+ throw typeError; // 回退到原来的错误处理逻辑
446
+ }
447
+ } else {
448
+ console.warn(chalk.yellow(`所有 schema 都无效,回退到 JSDoc 类型生成`));
449
+ // 继续执行 JSDoc 类型生成逻辑
450
+ }
451
+ } else {
452
+ // 非引用错误,继续执行 JSDoc 类型生成逻辑
453
+ }
454
+
455
+ // JSDoc 类型生成逻辑
456
+ console.warn(chalk.yellow(`警告: 模块 "${moduleName}" 的类型生成失败,跳过类型文件生成`));
457
+ console.warn(chalk.yellow('类型生成错误:'), typeError?.message || typeError);
458
+ console.warn(chalk.yellow('Schema 列表:'), Object.keys(module.schemas));
459
+
460
+ // 生成基于 JSDoc 参数的类型文件作为备选方案
461
+ const allReferencedTypes = [...new Set(module.endpoints.flatMap(e => e.referencedTypes))];
462
+ if (allReferencedTypes.length > 0) {
463
+ console.log(chalk.blue(`为模块 "${moduleName}" 生成基于 JSDoc 的类型定义...`));
464
+
465
+ // 创建类型定义映射
466
+ const typeDefinitions: { [typeName: string]: string } = {};
467
+
468
+ // 从 endpoints 中提取类型定义
469
+ module.endpoints.forEach(endpoint => {
470
+ // 处理 params 类型
471
+ if (endpoint.requestParamsTypeName && endpoint.jsdocParams) {
472
+ const paramsFields = endpoint.jsdocParams
473
+ .filter(param => param.name && param.type)
474
+ .map(param => {
475
+ const optional = !param.required ? '?' : '';
476
+ let type = param.type;
477
+
478
+ // 处理联合类型
479
+ if (type && type.includes(' | ')) {
480
+ // 如果包含 null,确保格式正确
481
+ if (type.includes('null')) {
482
+ type = type.replace(/\s*\|\s*null/g, ' | null');
483
+ }
484
+ } else {
485
+ // 处理基本类型转换
486
+ type = convertOpenApiTypeToTypeScript(type);
487
+ }
488
+
489
+ const comment = param.description ? ` // ${param.description}` : '';
490
+ return ` ${param.name}${optional}: ${type};${comment}`;
491
+ })
492
+ .join('\n');
493
+
494
+ if (paramsFields) {
495
+ typeDefinitions[endpoint.requestParamsTypeName] = `export interface ${endpoint.requestParamsTypeName} {
496
+ ${paramsFields}
497
+ }`;
498
+ }
499
+ }
500
+
501
+ // 处理 data 类型 (requestBody)
502
+ if (endpoint.requestBodyTypeName && endpoint.jsdocParams) {
503
+ const dataFields = endpoint.jsdocParams
504
+ .filter(param => param.name && param.type)
505
+ .map(param => {
506
+ const optional = !param.required ? '?' : '';
507
+ let type = param.type;
508
+
509
+ // 处理联合类型
510
+ if (type && type.includes(' | ')) {
511
+ // 如果包含 null,确保格式正确
512
+ if (type.includes('null')) {
513
+ type = type.replace(/\s*\|\s*null/g, ' | null');
514
+ }
515
+ } else {
516
+ // 处理基本类型转换
517
+ type = convertOpenApiTypeToTypeScript(type);
518
+ }
519
+
520
+ const comment = param.description ? ` // ${param.description}` : '';
521
+ return ` ${param.name}${optional}: ${type};${comment}`;
522
+ })
523
+ .join('\n');
524
+
525
+ if (dataFields) {
526
+ typeDefinitions[endpoint.requestBodyTypeName] = `export interface ${endpoint.requestBodyTypeName} {
527
+ ${dataFields}
528
+ }`;
529
+ }
530
+ }
531
+ });
532
+
533
+ // 为没有定义的类型添加基本定义
534
+ allReferencedTypes.forEach(typeName => {
535
+ if (!typeDefinitions[typeName]) {
536
+ typeDefinitions[typeName] = `export interface ${typeName} {
537
+ [key: string]: any;
538
+ }`;
539
+ }
540
+ });
541
+
542
+ const basicTypesContent = `/* eslint-disable */
543
+ /**
544
+ * This file was automatically generated by czh-api.
545
+ * Type definitions based on JSDoc parameters (fallback when schema compilation fails)
546
+ */
547
+
548
+ ${Object.values(typeDefinitions).join('\n\n')}
549
+ `;
550
+ fs.writeFileSync(path.join(moduleDir, 'types.ts'), basicTypesContent);
551
+ typesContent = basicTypesContent;
552
+ console.log(chalk.green(`✓ 模块 "${moduleName}" 基于 JSDoc 的类型文件生成成功`));
553
+ } else {
554
+ typesContent = '';
555
+ }
556
+ }
557
+ } else {
558
+ console.log(chalk.yellow(`模块 "${moduleName}" 没有 schema,跳过类型文件生成`));
121
559
  }
122
560
 
123
561
  // Generate API file
124
562
  const allReferencedTypes = [...new Set(module.endpoints.flatMap(e => e.referencedTypes))];
125
563
  const customImports = config.customImports || [`import http from "${config.httpClientPath}";`];
126
564
 
127
- let apiFileContent = '';
565
+ try {
566
+ let apiFileContent = '';
128
567
 
129
- if (module.description) {
130
- apiFileContent += `/**\n * @description ${module.description}\n */\n\n`;
131
- }
132
-
133
- apiFileContent += `${customImports.join('\n')}\n`;
134
-
135
- if (allReferencedTypes.length > 0) {
136
- apiFileContent += `import type { ${allReferencedTypes.join(', ')} } from './types';\n\n`;
568
+ if (module.description) {
569
+ apiFileContent += `/**\n * @description ${module.description}\n */\n\n`;
570
+ }
571
+
572
+ apiFileContent += `${customImports.join('\n')}\n`;
573
+
574
+ // 只导入存在的类型(包括 interface type)
575
+ const existingTypes = allReferencedTypes.filter(type =>
576
+ typesContent && (
577
+ typesContent.includes(`export interface ${type}`) ||
578
+ typesContent.includes(`export type ${type}`)
579
+ )
580
+ );
581
+
582
+ if (existingTypes.length > 0) {
583
+ apiFileContent += `import type { ${existingTypes.join(', ')} } from './types';\n\n`;
584
+ }
585
+
586
+ apiFileContent += module.endpoints.map(endpoint => {
587
+ try {
588
+ return apiTemplate(endpoint);
589
+ } catch (templateError: any) {
590
+ console.warn(chalk.yellow(`警告: 生成函数 "${endpoint.functionName}" 时出错,跳过此函数`));
591
+ console.warn(chalk.yellow('模板错误:'), templateError?.message || templateError);
592
+ return `// 函数 ${endpoint.functionName} 生成失败`;
593
+ }
594
+ }).join('\n\n');
595
+
596
+ fs.writeFileSync(path.join(moduleDir, `${moduleBaseName}.ts`), apiFileContent);
597
+
598
+ // Generate index.ts
599
+ const exportTypes = typesContent ? `\nexport * from './types';` : '';
600
+ fs.writeFileSync(path.join(moduleDir, 'index.ts'), `export * from './${moduleBaseName}';${exportTypes}\n`);
601
+
602
+ console.log(chalk.green(`✓ 模块 "${moduleName}" 生成成功 (${module.endpoints.length} 个接口)`));
603
+ } catch (moduleError: any) {
604
+ console.warn(chalk.yellow(`警告: 模块 "${moduleName}" 生成失败,跳过此模块`));
605
+ console.warn(chalk.yellow('模块生成错误:'), moduleError?.message || moduleError);
137
606
  }
138
-
139
- apiFileContent += module.endpoints.map(endpoint => apiTemplate(endpoint)).join('\n\n');
140
- fs.writeFileSync(path.join(moduleDir, `${moduleName}.ts`), apiFileContent);
141
-
142
- // Generate index.ts
143
- const exportTypes = typesContent ? `\nexport * from './types';` : '';
144
- fs.writeFileSync(path.join(moduleDir, 'index.ts'), `export * from './${moduleName}';${exportTypes}\n`);
145
607
  }
146
608
 
147
609
  console.log(chalk.green.bold('API code generated successfully!'));