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.
- package/CHANGELOG.md +30 -0
- package/README.md +110 -1
- package/dist/commands/build.js +456 -42
- package/dist/core/parser.js +126 -23
- package/package.json +2 -1
- package/src/commands/build.ts +516 -54
- package/src/core/parser.ts +128 -21
package/dist/commands/build.js
CHANGED
|
@@ -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:
|
|
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,163 @@ 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 improveTypeDefinitions(typesContent, endpoints) {
|
|
56
|
+
let improvedContent = typesContent;
|
|
57
|
+
// 收集所有需要改进的类型定义
|
|
58
|
+
const typeImprovements = {};
|
|
59
|
+
endpoints.forEach(endpoint => {
|
|
60
|
+
// 改进 params 类型
|
|
61
|
+
if (endpoint.requestParamsTypeName && endpoint.jsdocParams) {
|
|
62
|
+
const paramsFields = endpoint.jsdocParams
|
|
63
|
+
.filter((param) => param.name && param.type)
|
|
64
|
+
.map((param) => {
|
|
65
|
+
const optional = !param.required ? '?' : '';
|
|
66
|
+
let type = param.type;
|
|
67
|
+
// 处理联合类型,确保正确的 TypeScript 语法
|
|
68
|
+
if (type && type.includes(' | ')) {
|
|
69
|
+
// 如果包含 null,确保格式正确
|
|
70
|
+
if (type.includes('null')) {
|
|
71
|
+
type = type.replace(/\s*\|\s*null/g, ' | null');
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
else {
|
|
75
|
+
// 处理基本类型转换
|
|
76
|
+
type = convertOpenApiTypeToTypeScript(type);
|
|
77
|
+
}
|
|
78
|
+
const comment = param.description ? ` // ${param.description}` : '';
|
|
79
|
+
return ` ${param.name}${optional}: ${type};${comment}`;
|
|
80
|
+
})
|
|
81
|
+
.join('\n');
|
|
82
|
+
if (paramsFields) {
|
|
83
|
+
typeImprovements[endpoint.requestParamsTypeName] = `export interface ${endpoint.requestParamsTypeName} {
|
|
84
|
+
${paramsFields}
|
|
85
|
+
}`;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
// 改进 requestBody 类型
|
|
89
|
+
if (endpoint.requestBodyTypeName && endpoint.jsdocParams) {
|
|
90
|
+
const dataFields = endpoint.jsdocParams
|
|
91
|
+
.filter((param) => param.name && param.type)
|
|
92
|
+
.map((param) => {
|
|
93
|
+
const optional = !param.required ? '?' : '';
|
|
94
|
+
let type = param.type;
|
|
95
|
+
// 处理联合类型,确保正确的 TypeScript 语法
|
|
96
|
+
if (type && type.includes(' | ')) {
|
|
97
|
+
// 如果包含 null,确保格式正确
|
|
98
|
+
if (type.includes('null')) {
|
|
99
|
+
type = type.replace(/\s*\|\s*null/g, ' | null');
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
else {
|
|
103
|
+
// 处理基本类型转换
|
|
104
|
+
type = convertOpenApiTypeToTypeScript(type);
|
|
105
|
+
}
|
|
106
|
+
const comment = param.description ? ` // ${param.description}` : '';
|
|
107
|
+
return ` ${param.name}${optional}: ${type};${comment}`;
|
|
108
|
+
})
|
|
109
|
+
.join('\n');
|
|
110
|
+
if (dataFields) {
|
|
111
|
+
typeImprovements[endpoint.requestBodyTypeName] = `export interface ${endpoint.requestBodyTypeName} {
|
|
112
|
+
${dataFields}
|
|
113
|
+
}`;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
});
|
|
117
|
+
// 替换现有的类型定义
|
|
118
|
+
Object.entries(typeImprovements).forEach(([typeName, newDefinition]) => {
|
|
119
|
+
// 查找并替换现有的接口定义
|
|
120
|
+
const interfaceRegex = new RegExp(`export interface ${typeName}\\s*\\{[^}]*\\}`, 'g');
|
|
121
|
+
if (interfaceRegex.test(improvedContent)) {
|
|
122
|
+
improvedContent = improvedContent.replace(interfaceRegex, newDefinition);
|
|
123
|
+
}
|
|
124
|
+
else {
|
|
125
|
+
// 如果没找到接口定义,添加到末尾
|
|
126
|
+
improvedContent += '\n\n' + newDefinition;
|
|
127
|
+
}
|
|
128
|
+
});
|
|
129
|
+
return improvedContent;
|
|
130
|
+
}
|
|
131
|
+
// 移除无用的类型别名
|
|
132
|
+
function removeUselessTypeAliases(typesContent) {
|
|
133
|
+
let cleanedContent = typesContent;
|
|
134
|
+
// 收集要删除的类型别名及其对应的基础类型
|
|
135
|
+
const typeAliasMap = {};
|
|
136
|
+
// 匹配简单的类型别名 (export type Name = string;)
|
|
137
|
+
const simpleTypeAliasRegex = /export type (\w+\d*) = (string|number|boolean)(\s*\|\s*null)?;?\n?/g;
|
|
138
|
+
let match;
|
|
139
|
+
while ((match = simpleTypeAliasRegex.exec(cleanedContent)) !== null) {
|
|
140
|
+
const aliasName = match[1];
|
|
141
|
+
const baseType = match[2] + (match[3] || '');
|
|
142
|
+
typeAliasMap[aliasName] = baseType;
|
|
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
|
+
// 匹配空的 JSON 类型别名 (export type Name = {};)
|
|
152
|
+
const jsonTypeAliasRegex = /export type (\w+) = \{\}(\s*\|\s*null)?;?\n?/g;
|
|
153
|
+
while ((match = jsonTypeAliasRegex.exec(cleanedContent)) !== null) {
|
|
154
|
+
const aliasName = match[1];
|
|
155
|
+
const baseType = 'any' + (match[2] || '');
|
|
156
|
+
typeAliasMap[aliasName] = baseType;
|
|
157
|
+
}
|
|
158
|
+
// 安全地替换接口中对这些类型别名的引用
|
|
159
|
+
Object.entries(typeAliasMap).forEach(([aliasName, baseType]) => {
|
|
160
|
+
// 创建更精确的正则表达式,确保只替换完整的类型引用
|
|
161
|
+
const escapedAliasName = aliasName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
162
|
+
// 替换接口字段中的类型引用 (fieldName: AliasName;)
|
|
163
|
+
const fieldTypeRegex = new RegExp(`(\\w+\\??:\\s*)${escapedAliasName}(\\s*;)`, 'g');
|
|
164
|
+
cleanedContent = cleanedContent.replace(fieldTypeRegex, `$1${baseType}$2`);
|
|
165
|
+
// 替换接口字段中的类型引用,包括注释 (fieldName: AliasName; // comment)
|
|
166
|
+
const fieldTypeWithCommentRegex = new RegExp(`(\\w+\\??:\\s*)${escapedAliasName}(\\s*;\\s*//[^\\n]*)`, 'g');
|
|
167
|
+
cleanedContent = cleanedContent.replace(fieldTypeWithCommentRegex, `$1${baseType}$2`);
|
|
168
|
+
});
|
|
169
|
+
// 移除类型别名定义(按行移除,避免破坏其他内容)
|
|
170
|
+
const lines = cleanedContent.split('\n');
|
|
171
|
+
const filteredLines = lines.filter(line => {
|
|
172
|
+
// 移除简单类型别名
|
|
173
|
+
if (/^export type \w+\d* = (string|number|boolean)(\s*\|\s*null)?;?\s*$/.test(line)) {
|
|
174
|
+
return false;
|
|
175
|
+
}
|
|
176
|
+
// 移除空对象类型别名
|
|
177
|
+
if (/^export type \w+ = \{\}(\s*\|\s*null)?;?\s*$/.test(line)) {
|
|
178
|
+
return false;
|
|
179
|
+
}
|
|
180
|
+
return true;
|
|
181
|
+
});
|
|
182
|
+
cleanedContent = filteredLines.join('\n');
|
|
183
|
+
// 清理多余的空行和孤立的注释
|
|
184
|
+
cleanedContent = cleanedContent.replace(/\n{3,}/g, '\n\n');
|
|
185
|
+
// 移除孤立的类型注释(没有对应类型定义的注释)
|
|
186
|
+
cleanedContent = cleanedContent.replace(/\/\*\*\n \* [^\n]*\n \*\/\n(?!export)/g, '');
|
|
187
|
+
return cleanedContent;
|
|
188
|
+
}
|
|
32
189
|
const handleBuild = () => __awaiter(void 0, void 0, void 0, function* () {
|
|
33
190
|
try {
|
|
34
191
|
console.log(chalk_1.default.blue('Building API from source...'));
|
|
@@ -42,10 +199,57 @@ const handleBuild = () => __awaiter(void 0, void 0, void 0, function* () {
|
|
|
42
199
|
// Fetch and parse Swagger/OpenAPI document
|
|
43
200
|
console.log(chalk_1.default.blue(`正在从 ${config.url} 获取 API 文档...`));
|
|
44
201
|
const response = yield axios_1.default.get(config.url);
|
|
45
|
-
|
|
46
|
-
|
|
202
|
+
let apiDoc = response.data;
|
|
203
|
+
let isConverted = false;
|
|
204
|
+
// 检查是否为 OpenAPI 3.1.0,如果是则转换为 3.0.x
|
|
205
|
+
if (apiDoc.openapi && apiDoc.openapi.startsWith('3.1')) {
|
|
206
|
+
console.log(chalk_1.default.yellow('检测到 OpenAPI 3.1.0 规范,正在转换为 3.0.x...'));
|
|
207
|
+
try {
|
|
208
|
+
// 使用更保守的转换选项
|
|
209
|
+
const converter = new openapi_down_convert_1.Converter(apiDoc, {
|
|
210
|
+
verbose: false,
|
|
211
|
+
allOfTransform: false, // 不使用 allOf 转换,避免引用问题
|
|
212
|
+
deleteExampleWithId: false,
|
|
213
|
+
convertSchemaComments: false
|
|
214
|
+
});
|
|
215
|
+
apiDoc = converter.convert();
|
|
216
|
+
isConverted = true;
|
|
217
|
+
console.log(chalk_1.default.green('OpenAPI 3.1.0 已成功转换为 3.0.x'));
|
|
218
|
+
}
|
|
219
|
+
catch (convertError) {
|
|
220
|
+
console.warn(chalk_1.default.yellow('OpenAPI 版本转换失败,尝试直接解析原始文档...'));
|
|
221
|
+
console.warn(chalk_1.default.yellow('转换错误:'), (convertError === null || convertError === void 0 ? void 0 : convertError.message) || convertError);
|
|
222
|
+
// 如果转换失败,尝试直接使用原始文档
|
|
223
|
+
apiDoc = response.data;
|
|
224
|
+
isConverted = false;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
let api;
|
|
228
|
+
try {
|
|
229
|
+
api = yield SwaggerParser.bundle(apiDoc);
|
|
230
|
+
console.log(chalk_1.default.green('Swagger document fetched and bundled successfully.'));
|
|
231
|
+
}
|
|
232
|
+
catch (bundleError) {
|
|
233
|
+
console.warn(chalk_1.default.yellow('SwaggerParser.bundle 失败,尝试直接使用文档...'));
|
|
234
|
+
console.warn(chalk_1.default.yellow('Bundle 错误:'), (bundleError === null || bundleError === void 0 ? void 0 : bundleError.message) || bundleError);
|
|
235
|
+
// 如果 bundle 失败,直接使用转换后的文档
|
|
236
|
+
if (isConverted) {
|
|
237
|
+
console.log(chalk_1.default.blue('使用转换后的文档继续处理...'));
|
|
238
|
+
api = apiDoc;
|
|
239
|
+
}
|
|
240
|
+
else {
|
|
241
|
+
// 如果是 OpenAPI 3.1.0 且之前转换失败,提供更详细的错误信息
|
|
242
|
+
if (apiDoc.openapi && apiDoc.openapi.startsWith('3.1')) {
|
|
243
|
+
console.error(chalk_1.default.red('建议解决方案:'));
|
|
244
|
+
console.error(chalk_1.default.red('1. 检查 FastAPI 应用是否可以生成 OpenAPI 3.0.x 版本的文档'));
|
|
245
|
+
console.error(chalk_1.default.red('2. 或者在 FastAPI 中配置: app = FastAPI(openapi_version="3.0.2")'));
|
|
246
|
+
console.error(chalk_1.default.red('3. 或者使用 excludePaths 排除有问题的接口路径'));
|
|
247
|
+
}
|
|
248
|
+
throw bundleError;
|
|
249
|
+
}
|
|
250
|
+
}
|
|
47
251
|
// Process the API document
|
|
48
|
-
const modules = (0, parser_1.processApi)(api, config.excludePaths || [], config.includePaths || []);
|
|
252
|
+
const modules = (0, parser_1.processApi)(api, config.excludePaths || [], config.includePaths || [], config.pathPrefixes || []);
|
|
49
253
|
console.log(chalk_1.default.blue('API processed into modules.'));
|
|
50
254
|
// Clean output directory
|
|
51
255
|
const outputDir = path_1.default.join(process.cwd(), config.outputDir);
|
|
@@ -72,55 +276,265 @@ const handleBuild = () => __awaiter(void 0, void 0, void 0, function* () {
|
|
|
72
276
|
const module = modules[moduleName];
|
|
73
277
|
const moduleDir = path_1.default.join(outputDir, moduleName);
|
|
74
278
|
fs_1.default.mkdirSync(moduleDir, { recursive: true });
|
|
279
|
+
// 提取模块的基础名称(用于文件名)
|
|
280
|
+
const moduleBaseName = moduleName.includes('/')
|
|
281
|
+
? moduleName.split('/').pop()
|
|
282
|
+
: moduleName;
|
|
75
283
|
let typesContent = '';
|
|
76
284
|
// Generate types.ts
|
|
77
285
|
if (Object.keys(module.schemas).length > 0) {
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
schemasWithTitles
|
|
286
|
+
console.log(chalk_1.default.blue(`正在为模块 "${moduleName}" 生成类型文件,包含 ${Object.keys(module.schemas).length} 个 schema`));
|
|
287
|
+
try {
|
|
288
|
+
const schemasWithTitles = {};
|
|
289
|
+
for (const schemaName in module.schemas) {
|
|
290
|
+
schemasWithTitles[schemaName] = Object.assign(Object.assign({}, module.schemas[schemaName]), { title: schemaName });
|
|
291
|
+
}
|
|
292
|
+
const rootSchemaForCompiler = {
|
|
293
|
+
title: 'schemas',
|
|
294
|
+
type: 'object',
|
|
295
|
+
properties: {},
|
|
296
|
+
additionalProperties: false,
|
|
297
|
+
definitions: schemasWithTitles,
|
|
298
|
+
components: {
|
|
299
|
+
schemas: schemasWithTitles
|
|
300
|
+
}
|
|
301
|
+
};
|
|
302
|
+
console.log(chalk_1.default.blue(`开始编译 ${Object.keys(schemasWithTitles).length} 个类型定义...`));
|
|
303
|
+
typesContent = yield (0, json_schema_to_typescript_1.compile)(rootSchemaForCompiler, 'schemas', {
|
|
304
|
+
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*/',
|
|
305
|
+
unreachableDefinitions: true,
|
|
306
|
+
additionalProperties: false,
|
|
307
|
+
});
|
|
308
|
+
typesContent = typesContent.replace(/export interface \w+ \{\s*\}\n/g, '');
|
|
309
|
+
const refCommentRegex = /\/\*\*\n \* This interface was referenced by `\w+`'s JSON-Schema[\s\S]*?\*\/\n/g;
|
|
310
|
+
typesContent = typesContent.replace(refCommentRegex, '');
|
|
311
|
+
typesContent = typesContent.replace(/:\s*\{\}\[\]/g, ': any[]');
|
|
312
|
+
typesContent = typesContent.replace(/\[k: string\]: \{\}/g, '[k: string]: any');
|
|
313
|
+
const inlineIndexSignatureRegex = /:\s*\{\s*\/\*\*[\s\S]*?\*\/[\s\n\r]*\[k: string\]: any;\s*\};/g;
|
|
314
|
+
typesContent = typesContent.replace(inlineIndexSignatureRegex, ': any;');
|
|
315
|
+
typesContent = typesContent.replace(/: \{\}/g, ': any');
|
|
316
|
+
typesContent = typesContent.replace(/\}\n/g, '}\n\n');
|
|
317
|
+
// 移除无用的类型别名
|
|
318
|
+
typesContent = removeUselessTypeAliases(typesContent);
|
|
319
|
+
// 改进类型定义:用 JSDoc 信息补充接口定义
|
|
320
|
+
const improvedTypes = improveTypeDefinitions(typesContent, module.endpoints);
|
|
321
|
+
if (improvedTypes !== typesContent) {
|
|
322
|
+
typesContent = improvedTypes;
|
|
323
|
+
console.log(chalk_1.default.blue(`✓ 使用 JSDoc 信息改进了类型定义`));
|
|
324
|
+
}
|
|
325
|
+
fs_1.default.writeFileSync(path_1.default.join(moduleDir, 'types.ts'), typesContent);
|
|
326
|
+
console.log(chalk_1.default.green(`✓ 模块 "${moduleName}" 类型文件生成成功`));
|
|
81
327
|
}
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
328
|
+
catch (typeError) {
|
|
329
|
+
// 检查是否是引用错误
|
|
330
|
+
const errorMessage = (typeError === null || typeError === void 0 ? void 0 : typeError.message) || typeError;
|
|
331
|
+
if (errorMessage.includes('Missing $ref pointer')) {
|
|
332
|
+
console.warn(chalk_1.default.yellow(`警告: 模块 "${moduleName}" 存在 OpenAPI 引用错误,尝试过滤有问题的 schema...`));
|
|
333
|
+
console.warn(chalk_1.default.yellow('引用错误:'), errorMessage);
|
|
334
|
+
// 尝试逐个验证 schema,过滤掉有问题的
|
|
335
|
+
const validSchemas = {};
|
|
336
|
+
const invalidSchemas = [];
|
|
337
|
+
for (const schemaName in module.schemas) {
|
|
338
|
+
try {
|
|
339
|
+
// 尝试单独编译每个 schema
|
|
340
|
+
const testSchema = {
|
|
341
|
+
title: 'test',
|
|
342
|
+
type: 'object',
|
|
343
|
+
properties: {},
|
|
344
|
+
additionalProperties: false,
|
|
345
|
+
definitions: { [schemaName]: module.schemas[schemaName] },
|
|
346
|
+
components: { schemas: { [schemaName]: module.schemas[schemaName] } }
|
|
347
|
+
};
|
|
348
|
+
yield (0, json_schema_to_typescript_1.compile)(testSchema, 'test', { unreachableDefinitions: true });
|
|
349
|
+
validSchemas[schemaName] = Object.assign(Object.assign({}, module.schemas[schemaName]), { title: schemaName });
|
|
350
|
+
}
|
|
351
|
+
catch (schemaError) {
|
|
352
|
+
invalidSchemas.push(schemaName);
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
if (Object.keys(validSchemas).length > 0) {
|
|
356
|
+
console.log(chalk_1.default.blue(`找到 ${Object.keys(validSchemas).length} 个有效 schema,${invalidSchemas.length} 个无效 schema`));
|
|
357
|
+
if (invalidSchemas.length > 0) {
|
|
358
|
+
console.warn(chalk_1.default.yellow('无效的 schema:'), invalidSchemas.join(', '));
|
|
359
|
+
}
|
|
360
|
+
try {
|
|
361
|
+
const rootSchemaForCompiler = {
|
|
362
|
+
title: 'schemas',
|
|
363
|
+
type: 'object',
|
|
364
|
+
properties: {},
|
|
365
|
+
additionalProperties: false,
|
|
366
|
+
definitions: validSchemas,
|
|
367
|
+
components: { schemas: validSchemas }
|
|
368
|
+
};
|
|
369
|
+
typesContent = yield (0, json_schema_to_typescript_1.compile)(rootSchemaForCompiler, 'schemas', {
|
|
370
|
+
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*/',
|
|
371
|
+
unreachableDefinitions: true,
|
|
372
|
+
additionalProperties: false,
|
|
373
|
+
});
|
|
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
|
+
typesContent = removeUselessTypeAliases(typesContent);
|
|
385
|
+
const improvedTypes = improveTypeDefinitions(typesContent, module.endpoints);
|
|
386
|
+
if (improvedTypes !== typesContent) {
|
|
387
|
+
typesContent = improvedTypes;
|
|
388
|
+
console.log(chalk_1.default.blue(`✓ 使用 JSDoc 信息改进了类型定义`));
|
|
389
|
+
}
|
|
390
|
+
fs_1.default.writeFileSync(path_1.default.join(moduleDir, 'types.ts'), typesContent);
|
|
391
|
+
console.log(chalk_1.default.green(`✓ 模块 "${moduleName}" 部分类型文件生成成功 (${Object.keys(validSchemas).length}/${Object.keys(module.schemas).length} 个 schema)`));
|
|
392
|
+
}
|
|
393
|
+
catch (partialError) {
|
|
394
|
+
console.warn(chalk_1.default.yellow(`部分 schema 编译也失败,回退到 JSDoc 类型生成`));
|
|
395
|
+
throw typeError; // 回退到原来的错误处理逻辑
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
else {
|
|
399
|
+
console.warn(chalk_1.default.yellow(`所有 schema 都无效,回退到 JSDoc 类型生成`));
|
|
400
|
+
// 继续执行 JSDoc 类型生成逻辑
|
|
401
|
+
}
|
|
90
402
|
}
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
403
|
+
else {
|
|
404
|
+
// 非引用错误,继续执行 JSDoc 类型生成逻辑
|
|
405
|
+
}
|
|
406
|
+
// JSDoc 类型生成逻辑
|
|
407
|
+
console.warn(chalk_1.default.yellow(`警告: 模块 "${moduleName}" 的类型生成失败,跳过类型文件生成`));
|
|
408
|
+
console.warn(chalk_1.default.yellow('类型生成错误:'), (typeError === null || typeError === void 0 ? void 0 : typeError.message) || typeError);
|
|
409
|
+
console.warn(chalk_1.default.yellow('Schema 列表:'), Object.keys(module.schemas));
|
|
410
|
+
// 生成基于 JSDoc 参数的类型文件作为备选方案
|
|
411
|
+
const allReferencedTypes = [...new Set(module.endpoints.flatMap(e => e.referencedTypes))];
|
|
412
|
+
if (allReferencedTypes.length > 0) {
|
|
413
|
+
console.log(chalk_1.default.blue(`为模块 "${moduleName}" 生成基于 JSDoc 的类型定义...`));
|
|
414
|
+
// 创建类型定义映射
|
|
415
|
+
const typeDefinitions = {};
|
|
416
|
+
// 从 endpoints 中提取类型定义
|
|
417
|
+
module.endpoints.forEach(endpoint => {
|
|
418
|
+
// 处理 params 类型
|
|
419
|
+
if (endpoint.requestParamsTypeName && endpoint.jsdocParams) {
|
|
420
|
+
const paramsFields = endpoint.jsdocParams
|
|
421
|
+
.filter(param => param.name && param.type)
|
|
422
|
+
.map(param => {
|
|
423
|
+
const optional = !param.required ? '?' : '';
|
|
424
|
+
let type = param.type;
|
|
425
|
+
// 处理联合类型
|
|
426
|
+
if (type && type.includes(' | ')) {
|
|
427
|
+
// 如果包含 null,确保格式正确
|
|
428
|
+
if (type.includes('null')) {
|
|
429
|
+
type = type.replace(/\s*\|\s*null/g, ' | null');
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
else {
|
|
433
|
+
// 处理基本类型转换
|
|
434
|
+
type = convertOpenApiTypeToTypeScript(type);
|
|
435
|
+
}
|
|
436
|
+
const comment = param.description ? ` // ${param.description}` : '';
|
|
437
|
+
return ` ${param.name}${optional}: ${type};${comment}`;
|
|
438
|
+
})
|
|
439
|
+
.join('\n');
|
|
440
|
+
if (paramsFields) {
|
|
441
|
+
typeDefinitions[endpoint.requestParamsTypeName] = `export interface ${endpoint.requestParamsTypeName} {
|
|
442
|
+
${paramsFields}
|
|
443
|
+
}`;
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
// 处理 data 类型 (requestBody)
|
|
447
|
+
if (endpoint.requestBodyTypeName && endpoint.jsdocParams) {
|
|
448
|
+
const dataFields = endpoint.jsdocParams
|
|
449
|
+
.filter(param => param.name && param.type)
|
|
450
|
+
.map(param => {
|
|
451
|
+
const optional = !param.required ? '?' : '';
|
|
452
|
+
let type = param.type;
|
|
453
|
+
// 处理联合类型
|
|
454
|
+
if (type && type.includes(' | ')) {
|
|
455
|
+
// 如果包含 null,确保格式正确
|
|
456
|
+
if (type.includes('null')) {
|
|
457
|
+
type = type.replace(/\s*\|\s*null/g, ' | null');
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
else {
|
|
461
|
+
// 处理基本类型转换
|
|
462
|
+
type = convertOpenApiTypeToTypeScript(type);
|
|
463
|
+
}
|
|
464
|
+
const comment = param.description ? ` // ${param.description}` : '';
|
|
465
|
+
return ` ${param.name}${optional}: ${type};${comment}`;
|
|
466
|
+
})
|
|
467
|
+
.join('\n');
|
|
468
|
+
if (dataFields) {
|
|
469
|
+
typeDefinitions[endpoint.requestBodyTypeName] = `export interface ${endpoint.requestBodyTypeName} {
|
|
470
|
+
${dataFields}
|
|
471
|
+
}`;
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
});
|
|
475
|
+
// 为没有定义的类型添加基本定义
|
|
476
|
+
allReferencedTypes.forEach(typeName => {
|
|
477
|
+
if (!typeDefinitions[typeName]) {
|
|
478
|
+
typeDefinitions[typeName] = `export interface ${typeName} {
|
|
479
|
+
[key: string]: any;
|
|
480
|
+
}`;
|
|
481
|
+
}
|
|
482
|
+
});
|
|
483
|
+
const basicTypesContent = `/* eslint-disable */
|
|
484
|
+
/**
|
|
485
|
+
* This file was automatically generated by czh-api.
|
|
486
|
+
* Type definitions based on JSDoc parameters (fallback when schema compilation fails)
|
|
487
|
+
*/
|
|
488
|
+
|
|
489
|
+
${Object.values(typeDefinitions).join('\n\n')}
|
|
490
|
+
`;
|
|
491
|
+
fs_1.default.writeFileSync(path_1.default.join(moduleDir, 'types.ts'), basicTypesContent);
|
|
492
|
+
typesContent = basicTypesContent;
|
|
493
|
+
console.log(chalk_1.default.green(`✓ 模块 "${moduleName}" 基于 JSDoc 的类型文件生成成功`));
|
|
494
|
+
}
|
|
495
|
+
else {
|
|
496
|
+
typesContent = '';
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
else {
|
|
501
|
+
console.log(chalk_1.default.yellow(`模块 "${moduleName}" 没有 schema,跳过类型文件生成`));
|
|
107
502
|
}
|
|
108
503
|
// Generate API file
|
|
109
504
|
const allReferencedTypes = [...new Set(module.endpoints.flatMap(e => e.referencedTypes))];
|
|
110
505
|
const customImports = config.customImports || [`import http from "${config.httpClientPath}";`];
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
506
|
+
try {
|
|
507
|
+
let apiFileContent = '';
|
|
508
|
+
if (module.description) {
|
|
509
|
+
apiFileContent += `/**\n * @description ${module.description}\n */\n\n`;
|
|
510
|
+
}
|
|
511
|
+
apiFileContent += `${customImports.join('\n')}\n`;
|
|
512
|
+
// 只导入存在的类型(包括 interface 和 type)
|
|
513
|
+
const existingTypes = allReferencedTypes.filter(type => typesContent && (typesContent.includes(`export interface ${type}`) ||
|
|
514
|
+
typesContent.includes(`export type ${type}`)));
|
|
515
|
+
if (existingTypes.length > 0) {
|
|
516
|
+
apiFileContent += `import type { ${existingTypes.join(', ')} } from './types';\n\n`;
|
|
517
|
+
}
|
|
518
|
+
apiFileContent += module.endpoints.map(endpoint => {
|
|
519
|
+
try {
|
|
520
|
+
return apiTemplate(endpoint);
|
|
521
|
+
}
|
|
522
|
+
catch (templateError) {
|
|
523
|
+
console.warn(chalk_1.default.yellow(`警告: 生成函数 "${endpoint.functionName}" 时出错,跳过此函数`));
|
|
524
|
+
console.warn(chalk_1.default.yellow('模板错误:'), (templateError === null || templateError === void 0 ? void 0 : templateError.message) || templateError);
|
|
525
|
+
return `// 函数 ${endpoint.functionName} 生成失败`;
|
|
526
|
+
}
|
|
527
|
+
}).join('\n\n');
|
|
528
|
+
fs_1.default.writeFileSync(path_1.default.join(moduleDir, `${moduleBaseName}.ts`), apiFileContent);
|
|
529
|
+
// Generate index.ts
|
|
530
|
+
const exportTypes = typesContent ? `\nexport * from './types';` : '';
|
|
531
|
+
fs_1.default.writeFileSync(path_1.default.join(moduleDir, 'index.ts'), `export * from './${moduleBaseName}';${exportTypes}\n`);
|
|
532
|
+
console.log(chalk_1.default.green(`✓ 模块 "${moduleName}" 生成成功 (${module.endpoints.length} 个接口)`));
|
|
114
533
|
}
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
534
|
+
catch (moduleError) {
|
|
535
|
+
console.warn(chalk_1.default.yellow(`警告: 模块 "${moduleName}" 生成失败,跳过此模块`));
|
|
536
|
+
console.warn(chalk_1.default.yellow('模块生成错误:'), (moduleError === null || moduleError === void 0 ? void 0 : moduleError.message) || moduleError);
|
|
118
537
|
}
|
|
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`);
|
|
124
538
|
}
|
|
125
539
|
console.log(chalk_1.default.green.bold('API code generated successfully!'));
|
|
126
540
|
}
|