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