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