czh-api 1.0.5 → 1.0.6

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,8 +18,8 @@ import { compile } from 'json-schema-to-typescript';
18
18
  import { Converter } from '@apiture/openapi-down-convert';
19
19
 
20
20
  // 统一的类型转换函数
21
- function convertOpenApiTypeToTypeScript(openApiType: string | undefined): string {
22
- if (!openApiType) return 'any';
21
+ function convertOpenApiTypeToTypeScript(openApiType: string | undefined): string {
22
+ if (!openApiType) return 'any';
23
23
 
24
24
  switch (openApiType) {
25
25
  case 'integer':
@@ -32,230 +32,230 @@ function convertOpenApiTypeToTypeScript(openApiType: string | undefined): string
32
32
  return 'boolean';
33
33
  case 'string':
34
34
  return 'string';
35
- case 'number':
36
- return 'number';
37
- default:
38
- return openApiType;
39
- }
40
- }
35
+ case 'number':
36
+ return 'number';
37
+ default:
38
+ return openApiType;
39
+ }
40
+ }
41
41
 
42
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
- const TS_BUILTIN_TYPE_NAMES = new Set([
89
- 'string',
90
- 'number',
91
- 'boolean',
92
- 'null',
93
- 'undefined',
94
- 'void',
95
- 'any',
96
- 'unknown',
97
- 'never',
98
- 'object',
99
- 'Record',
100
- 'Array',
101
- 'Date',
102
- 'Promise',
103
- 'true',
104
- 'false',
105
- ]);
106
-
107
- function extractTypeIdentifiers(typeExpr: string | undefined): string[] {
108
- if (!typeExpr) return [];
109
- const matches = typeExpr.match(/[A-Za-z_$][A-Za-z0-9_$]*/g) || [];
110
- return [...new Set(matches.filter(name => !TS_BUILTIN_TYPE_NAMES.has(name)))];
111
- }
112
-
113
- function getSchemaNameFromRef(ref: string | undefined): string {
114
- if (!ref) return '';
115
- return ref.split('/').pop() || '';
116
- }
117
-
118
- function resolveSchemaTypeFromModuleSchemas(
119
- schema: OpenAPIV3.SchemaObject | OpenAPIV3.ReferenceObject | undefined,
120
- moduleSchemas: { [typeName: string]: OpenAPIV3.SchemaObject },
121
- visitedRefs: Set<string> = new Set()
122
- ): string {
123
- if (!schema) return 'any';
124
-
125
- if ((schema as OpenAPIV3.ReferenceObject).$ref) {
126
- const refName = getSchemaNameFromRef((schema as OpenAPIV3.ReferenceObject).$ref);
127
- return refName || 'any';
128
- }
129
-
130
- const schemaObj = schema as OpenAPIV3.SchemaObject;
131
-
132
- if (schemaObj.anyOf && Array.isArray(schemaObj.anyOf) && schemaObj.anyOf.length > 0) {
133
- const types = schemaObj.anyOf
134
- .map(item => resolveSchemaTypeFromModuleSchemas(item as OpenAPIV3.SchemaObject | OpenAPIV3.ReferenceObject, moduleSchemas, visitedRefs))
135
- .filter(Boolean);
136
- if (types.length > 0) {
137
- return [...new Set(types)].join(' | ');
138
- }
139
- }
140
-
141
- if (schemaObj.oneOf && Array.isArray(schemaObj.oneOf) && schemaObj.oneOf.length > 0) {
142
- const types = schemaObj.oneOf
143
- .map(item => resolveSchemaTypeFromModuleSchemas(item as OpenAPIV3.SchemaObject | OpenAPIV3.ReferenceObject, moduleSchemas, visitedRefs))
144
- .filter(Boolean);
145
- if (types.length > 0) {
146
- return [...new Set(types)].join(' | ');
147
- }
148
- }
149
-
150
- if (schemaObj.type === 'array') {
151
- const itemType = resolveSchemaTypeFromModuleSchemas(
152
- schemaObj.items as OpenAPIV3.SchemaObject | OpenAPIV3.ReferenceObject | undefined,
153
- moduleSchemas,
154
- visitedRefs
155
- );
156
- return `${itemType || 'any'}[]`;
157
- }
158
-
159
- if (schemaObj.type === 'object' && schemaObj.additionalProperties) {
160
- if (schemaObj.additionalProperties === true) {
161
- return 'Record<string, any>';
162
- }
163
- const valueType = resolveSchemaTypeFromModuleSchemas(
164
- schemaObj.additionalProperties as OpenAPIV3.SchemaObject | OpenAPIV3.ReferenceObject,
165
- moduleSchemas,
166
- visitedRefs
167
- );
168
- return `Record<string, ${valueType || 'any'}>`;
169
- }
170
-
171
- if (schemaObj.type === 'object' && schemaObj.properties && Object.keys(schemaObj.properties).length > 0) {
172
- const requiredSet = new Set(schemaObj.required || []);
173
- const inlineFields = Object.entries(schemaObj.properties).map(([key, value]) => {
174
- const fieldType = resolveSchemaTypeFromModuleSchemas(
175
- value as OpenAPIV3.SchemaObject | OpenAPIV3.ReferenceObject,
176
- moduleSchemas,
177
- visitedRefs
178
- );
179
- const optional = requiredSet.has(key) ? '' : '?';
180
- return `${key}${optional}: ${fieldType || 'any'}`;
181
- });
182
- return `{ ${inlineFields.join('; ')} }`;
183
- }
184
-
185
- const baseType = convertOpenApiTypeToTypeScript(schemaObj.type);
186
- if (!baseType) return 'any';
187
- return schemaObj.nullable ? `${baseType} | null` : baseType;
188
- }
189
-
190
- function collectSchemaProperties(
191
- schema: OpenAPIV3.SchemaObject | OpenAPIV3.ReferenceObject | undefined,
192
- moduleSchemas: { [typeName: string]: OpenAPIV3.SchemaObject },
193
- visitedRefs: Set<string> = new Set()
194
- ): { properties: Record<string, OpenAPIV3.SchemaObject | OpenAPIV3.ReferenceObject>; required: Set<string> } {
195
- const collected: { properties: Record<string, OpenAPIV3.SchemaObject | OpenAPIV3.ReferenceObject>; required: Set<string> } = {
196
- properties: {},
197
- required: new Set<string>(),
198
- };
199
- if (!schema) return collected;
200
-
201
- if ((schema as OpenAPIV3.ReferenceObject).$ref) {
202
- const ref = (schema as OpenAPIV3.ReferenceObject).$ref;
203
- if (!visitedRefs.has(ref)) {
204
- visitedRefs.add(ref);
205
- const refName = getSchemaNameFromRef(ref);
206
- const refSchema = moduleSchemas[refName];
207
- if (refSchema) {
208
- const nested = collectSchemaProperties(refSchema, moduleSchemas, visitedRefs);
209
- Object.assign(collected.properties, nested.properties);
210
- nested.required.forEach(field => collected.required.add(field));
211
- }
212
- }
213
- return collected;
214
- }
215
-
216
- const schemaObj = schema as OpenAPIV3.SchemaObject;
217
-
218
- if (schemaObj.allOf && Array.isArray(schemaObj.allOf)) {
219
- schemaObj.allOf.forEach(item => {
220
- const nested = collectSchemaProperties(item as OpenAPIV3.SchemaObject | OpenAPIV3.ReferenceObject, moduleSchemas, visitedRefs);
221
- Object.assign(collected.properties, nested.properties);
222
- nested.required.forEach(field => collected.required.add(field));
223
- });
224
- }
225
-
226
- if (schemaObj.properties) {
227
- Object.entries(schemaObj.properties).forEach(([name, value]) => {
228
- collected.properties[name] = value as OpenAPIV3.SchemaObject | OpenAPIV3.ReferenceObject;
229
- });
230
- }
231
-
232
- (schemaObj.required || []).forEach(field => collected.required.add(field));
233
- return collected;
234
- }
235
-
236
- function buildInterfaceDefinitionFromModuleSchemas(
237
- typeName: string,
238
- moduleSchemas: { [typeName: string]: OpenAPIV3.SchemaObject }
239
- ): string | null {
240
- const schema = moduleSchemas[typeName];
241
- if (!schema) return null;
242
-
243
- const { properties, required } = collectSchemaProperties(schema, moduleSchemas);
244
- const entries = Object.entries(properties);
245
- if (entries.length === 0) return null;
246
-
247
- const lines = entries.map(([propName, propSchema]) => {
248
- const optional = required.has(propName) ? '' : '?';
249
- const propType = resolveSchemaTypeFromModuleSchemas(propSchema, moduleSchemas) || 'any';
250
- const propDescription = (propSchema as OpenAPIV3.SchemaObject).description || '';
251
- const comment = propDescription ? ` // ${propDescription}` : '';
252
- return ` ${propName}${optional}: ${propType};${comment}`;
253
- });
254
-
255
- return `export interface ${typeName} {\n${lines.join('\n')}\n}`;
256
- }
257
-
258
- function improveTypeDefinitions(typesContent: string, endpoints: any[]): string {
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
+ const TS_BUILTIN_TYPE_NAMES = new Set([
89
+ 'string',
90
+ 'number',
91
+ 'boolean',
92
+ 'null',
93
+ 'undefined',
94
+ 'void',
95
+ 'any',
96
+ 'unknown',
97
+ 'never',
98
+ 'object',
99
+ 'Record',
100
+ 'Array',
101
+ 'Date',
102
+ 'Promise',
103
+ 'true',
104
+ 'false',
105
+ ]);
106
+
107
+ function extractTypeIdentifiers(typeExpr: string | undefined): string[] {
108
+ if (!typeExpr) return [];
109
+ const matches = typeExpr.match(/[A-Za-z_$][A-Za-z0-9_$]*/g) || [];
110
+ return [...new Set(matches.filter(name => !TS_BUILTIN_TYPE_NAMES.has(name)))];
111
+ }
112
+
113
+ function getSchemaNameFromRef(ref: string | undefined): string {
114
+ if (!ref) return '';
115
+ return ref.split('/').pop() || '';
116
+ }
117
+
118
+ function resolveSchemaTypeFromModuleSchemas(
119
+ schema: OpenAPIV3.SchemaObject | OpenAPIV3.ReferenceObject | undefined,
120
+ moduleSchemas: { [typeName: string]: OpenAPIV3.SchemaObject },
121
+ visitedRefs: Set<string> = new Set()
122
+ ): string {
123
+ if (!schema) return 'any';
124
+
125
+ if ((schema as OpenAPIV3.ReferenceObject).$ref) {
126
+ const refName = getSchemaNameFromRef((schema as OpenAPIV3.ReferenceObject).$ref);
127
+ return refName || 'any';
128
+ }
129
+
130
+ const schemaObj = schema as OpenAPIV3.SchemaObject;
131
+
132
+ if (schemaObj.anyOf && Array.isArray(schemaObj.anyOf) && schemaObj.anyOf.length > 0) {
133
+ const types = schemaObj.anyOf
134
+ .map(item => resolveSchemaTypeFromModuleSchemas(item as OpenAPIV3.SchemaObject | OpenAPIV3.ReferenceObject, moduleSchemas, visitedRefs))
135
+ .filter(Boolean);
136
+ if (types.length > 0) {
137
+ return [...new Set(types)].join(' | ');
138
+ }
139
+ }
140
+
141
+ if (schemaObj.oneOf && Array.isArray(schemaObj.oneOf) && schemaObj.oneOf.length > 0) {
142
+ const types = schemaObj.oneOf
143
+ .map(item => resolveSchemaTypeFromModuleSchemas(item as OpenAPIV3.SchemaObject | OpenAPIV3.ReferenceObject, moduleSchemas, visitedRefs))
144
+ .filter(Boolean);
145
+ if (types.length > 0) {
146
+ return [...new Set(types)].join(' | ');
147
+ }
148
+ }
149
+
150
+ if (schemaObj.type === 'array') {
151
+ const itemType = resolveSchemaTypeFromModuleSchemas(
152
+ schemaObj.items as OpenAPIV3.SchemaObject | OpenAPIV3.ReferenceObject | undefined,
153
+ moduleSchemas,
154
+ visitedRefs
155
+ );
156
+ return `${itemType || 'any'}[]`;
157
+ }
158
+
159
+ if (schemaObj.type === 'object' && schemaObj.additionalProperties) {
160
+ if (schemaObj.additionalProperties === true) {
161
+ return 'Record<string, any>';
162
+ }
163
+ const valueType = resolveSchemaTypeFromModuleSchemas(
164
+ schemaObj.additionalProperties as OpenAPIV3.SchemaObject | OpenAPIV3.ReferenceObject,
165
+ moduleSchemas,
166
+ visitedRefs
167
+ );
168
+ return `Record<string, ${valueType || 'any'}>`;
169
+ }
170
+
171
+ if (schemaObj.type === 'object' && schemaObj.properties && Object.keys(schemaObj.properties).length > 0) {
172
+ const requiredSet = new Set(schemaObj.required || []);
173
+ const inlineFields = Object.entries(schemaObj.properties).map(([key, value]) => {
174
+ const fieldType = resolveSchemaTypeFromModuleSchemas(
175
+ value as OpenAPIV3.SchemaObject | OpenAPIV3.ReferenceObject,
176
+ moduleSchemas,
177
+ visitedRefs
178
+ );
179
+ const optional = requiredSet.has(key) ? '' : '?';
180
+ return `${key}${optional}: ${fieldType || 'any'}`;
181
+ });
182
+ return `{ ${inlineFields.join('; ')} }`;
183
+ }
184
+
185
+ const baseType = convertOpenApiTypeToTypeScript(schemaObj.type);
186
+ if (!baseType) return 'any';
187
+ return schemaObj.nullable ? `${baseType} | null` : baseType;
188
+ }
189
+
190
+ function collectSchemaProperties(
191
+ schema: OpenAPIV3.SchemaObject | OpenAPIV3.ReferenceObject | undefined,
192
+ moduleSchemas: { [typeName: string]: OpenAPIV3.SchemaObject },
193
+ visitedRefs: Set<string> = new Set()
194
+ ): { properties: Record<string, OpenAPIV3.SchemaObject | OpenAPIV3.ReferenceObject>; required: Set<string> } {
195
+ const collected: { properties: Record<string, OpenAPIV3.SchemaObject | OpenAPIV3.ReferenceObject>; required: Set<string> } = {
196
+ properties: {},
197
+ required: new Set<string>(),
198
+ };
199
+ if (!schema) return collected;
200
+
201
+ if ((schema as OpenAPIV3.ReferenceObject).$ref) {
202
+ const ref = (schema as OpenAPIV3.ReferenceObject).$ref;
203
+ if (!visitedRefs.has(ref)) {
204
+ visitedRefs.add(ref);
205
+ const refName = getSchemaNameFromRef(ref);
206
+ const refSchema = moduleSchemas[refName];
207
+ if (refSchema) {
208
+ const nested = collectSchemaProperties(refSchema, moduleSchemas, visitedRefs);
209
+ Object.assign(collected.properties, nested.properties);
210
+ nested.required.forEach(field => collected.required.add(field));
211
+ }
212
+ }
213
+ return collected;
214
+ }
215
+
216
+ const schemaObj = schema as OpenAPIV3.SchemaObject;
217
+
218
+ if (schemaObj.allOf && Array.isArray(schemaObj.allOf)) {
219
+ schemaObj.allOf.forEach(item => {
220
+ const nested = collectSchemaProperties(item as OpenAPIV3.SchemaObject | OpenAPIV3.ReferenceObject, moduleSchemas, visitedRefs);
221
+ Object.assign(collected.properties, nested.properties);
222
+ nested.required.forEach(field => collected.required.add(field));
223
+ });
224
+ }
225
+
226
+ if (schemaObj.properties) {
227
+ Object.entries(schemaObj.properties).forEach(([name, value]) => {
228
+ collected.properties[name] = value as OpenAPIV3.SchemaObject | OpenAPIV3.ReferenceObject;
229
+ });
230
+ }
231
+
232
+ (schemaObj.required || []).forEach(field => collected.required.add(field));
233
+ return collected;
234
+ }
235
+
236
+ function buildInterfaceDefinitionFromModuleSchemas(
237
+ typeName: string,
238
+ moduleSchemas: { [typeName: string]: OpenAPIV3.SchemaObject }
239
+ ): string | null {
240
+ const schema = moduleSchemas[typeName];
241
+ if (!schema) return null;
242
+
243
+ const { properties, required } = collectSchemaProperties(schema, moduleSchemas);
244
+ const entries = Object.entries(properties);
245
+ if (entries.length === 0) return null;
246
+
247
+ const lines = entries.map(([propName, propSchema]) => {
248
+ const optional = required.has(propName) ? '' : '?';
249
+ const propType = resolveSchemaTypeFromModuleSchemas(propSchema, moduleSchemas) || 'any';
250
+ const propDescription = (propSchema as OpenAPIV3.SchemaObject).description || '';
251
+ const comment = propDescription ? ` // ${propDescription}` : '';
252
+ return ` ${propName}${optional}: ${propType};${comment}`;
253
+ });
254
+
255
+ return `export interface ${typeName} {\n${lines.join('\n')}\n}`;
256
+ }
257
+
258
+ function improveTypeDefinitions(typesContent: string, endpoints: any[]): string {
259
259
  let improvedContent = typesContent;
260
260
 
261
261
  // 收集所有需要改进的类型定义
@@ -270,13 +270,13 @@ function improveTypeDefinitions(typesContent: string, endpoints: any[]): string
270
270
  let type = param.type;
271
271
 
272
272
  // 处理联合类型,确保正确的 TypeScript 语法
273
- if (type && type.includes(' | ')) {
274
- if (type.includes('null')) {
275
- type = type.replace(/\s*\|\s*null/g, ' | null');
276
- }
277
- } else {
278
- type = convertOpenApiTypeToTypeScript(type);
279
- }
273
+ if (type && type.includes(' | ')) {
274
+ if (type.includes('null')) {
275
+ type = type.replace(/\s*\|\s*null/g, ' | null');
276
+ }
277
+ } else {
278
+ type = convertOpenApiTypeToTypeScript(type);
279
+ }
280
280
 
281
281
  const comment = param.description ? ` // ${param.description}` : '';
282
282
  return ` ${param.name}${optional}: ${type};${comment}`;
@@ -286,52 +286,52 @@ function improveTypeDefinitions(typesContent: string, endpoints: any[]): string
286
286
 
287
287
  endpoints.forEach(endpoint => {
288
288
  // 改进 params 类型 - 使用 paramsJsdocParams(仅包含 path/query 参数)
289
- const requestParamsTypeName = endpoint.requestParamsTypeName;
290
- if (requestParamsTypeName && isValidTypeIdentifier(requestParamsTypeName) && endpoint.paramsJsdocParams && endpoint.paramsJsdocParams.length > 0) {
291
- const paramsFields = buildFieldsFromJsdocParams(endpoint.paramsJsdocParams);
292
-
293
- if (paramsFields) {
294
- typeImprovements[requestParamsTypeName] = `export interface ${requestParamsTypeName} {
295
- ${paramsFields}
296
- }`;
297
- }
298
- }
289
+ const requestParamsTypeName = endpoint.requestParamsTypeName;
290
+ if (requestParamsTypeName && isValidTypeIdentifier(requestParamsTypeName) && endpoint.paramsJsdocParams && endpoint.paramsJsdocParams.length > 0) {
291
+ const paramsFields = buildFieldsFromJsdocParams(endpoint.paramsJsdocParams);
292
+
293
+ if (paramsFields) {
294
+ typeImprovements[requestParamsTypeName] = `export interface ${requestParamsTypeName} {
295
+ ${paramsFields}
296
+ }`;
297
+ }
298
+ }
299
299
 
300
300
  // 改进 requestBody 类型 - 使用 dataJsdocParams(仅包含 requestBody 参数)
301
- const requestBodyTypeName = endpoint.requestBodyTypeName;
302
- if (requestBodyTypeName && isValidTypeIdentifier(requestBodyTypeName) && endpoint.dataJsdocParams && endpoint.dataJsdocParams.length > 0) {
303
- const dataFields = buildFieldsFromJsdocParams(endpoint.dataJsdocParams);
304
-
305
- if (dataFields) {
306
- typeImprovements[requestBodyTypeName] = `export interface ${requestBodyTypeName} {
307
- ${dataFields}
308
- }`;
309
- }
310
- }
301
+ const requestBodyTypeName = endpoint.requestBodyTypeName;
302
+ if (requestBodyTypeName && isValidTypeIdentifier(requestBodyTypeName) && endpoint.dataJsdocParams && endpoint.dataJsdocParams.length > 0) {
303
+ const dataFields = buildFieldsFromJsdocParams(endpoint.dataJsdocParams);
304
+
305
+ if (dataFields) {
306
+ typeImprovements[requestBodyTypeName] = `export interface ${requestBodyTypeName} {
307
+ ${dataFields}
308
+ }`;
309
+ }
310
+ }
311
311
 
312
312
  // 改进 response 类型 - 使用 responseJsdocParams(包含响应字段信息)
313
- const responseTargetType = resolveInterfaceTargetType(endpoint.responseTypeName);
314
- if (responseTargetType && endpoint.responseTypeName !== 'void' && endpoint.responseJsdocParams && endpoint.responseJsdocParams.length > 0) {
315
- const responseFields = buildFieldsFromJsdocParams(endpoint.responseJsdocParams);
316
-
317
- if (responseFields) {
318
- typeImprovements[responseTargetType] = `export interface ${responseTargetType} {
319
- ${responseFields}
320
- }`;
321
- }
322
- }
313
+ const responseTargetType = resolveInterfaceTargetType(endpoint.responseTypeName);
314
+ if (responseTargetType && endpoint.responseTypeName !== 'void' && endpoint.responseJsdocParams && endpoint.responseJsdocParams.length > 0) {
315
+ const responseFields = buildFieldsFromJsdocParams(endpoint.responseJsdocParams);
316
+
317
+ if (responseFields) {
318
+ typeImprovements[responseTargetType] = `export interface ${responseTargetType} {
319
+ ${responseFields}
320
+ }`;
321
+ }
322
+ }
323
323
  });
324
324
 
325
325
  // 替换现有的类型定义
326
326
  Object.entries(typeImprovements).forEach(([typeName, newDefinition]) => {
327
327
  // 查找并替换现有的接口定义
328
- const blockRange = findInterfaceBlockRange(improvedContent, typeName);
329
- if (blockRange) {
330
- improvedContent =
331
- improvedContent.slice(0, blockRange.start) +
332
- newDefinition +
333
- improvedContent.slice(blockRange.end);
334
- } else {
328
+ const blockRange = findInterfaceBlockRange(improvedContent, typeName);
329
+ if (blockRange) {
330
+ improvedContent =
331
+ improvedContent.slice(0, blockRange.start) +
332
+ newDefinition +
333
+ improvedContent.slice(blockRange.end);
334
+ } else {
335
335
  // 如果没找到接口定义,添加到末尾
336
336
  improvedContent += '\n\n' + newDefinition;
337
337
  }
@@ -341,7 +341,7 @@ ${responseFields}
341
341
  }
342
342
 
343
343
  // 移除无用的类型别名
344
- function removeUselessTypeAliases(typesContent: string): string {
344
+ function removeUselessTypeAliases(typesContent: string): string {
345
345
  let cleanedContent = typesContent;
346
346
 
347
347
  // 收集要删除的类型别名及其对应的基础类型
@@ -408,33 +408,57 @@ function removeUselessTypeAliases(typesContent: string): string {
408
408
  // 移除孤立的类型注释(没有对应类型定义的注释)
409
409
  cleanedContent = cleanedContent.replace(/\/\*\*\n \* [^\n]*\n \*\/\n(?!export)/g, '');
410
410
 
411
- return cleanedContent;
412
- }
413
-
414
- function normalizeRef(refValue: string): string {
415
- const match = refValue.match(/^#\/components\/schemas\/([^/]+)$/);
416
- if (match) {
417
- return `#/definitions/${match[1]}`;
418
- }
419
- return refValue;
411
+ return cleanedContent;
412
+ }
413
+
414
+ function normalizeRef(refValue: string): string {
415
+ const match = refValue.match(/^#\/components\/schemas\/([^/]+)$/);
416
+ if (match) {
417
+ return `#/definitions/${match[1]}`;
418
+ }
419
+ return refValue;
420
+ }
421
+
422
+ function normalizeSchemaRefs<T>(value: T): T {
423
+ if (Array.isArray(value)) {
424
+ return value.map(item => normalizeSchemaRefs(item)) as T;
425
+ }
426
+
427
+ if (value && typeof value === 'object') {
428
+ const obj = value as Record<string, unknown>;
429
+ const normalized: Record<string, unknown> = {};
430
+
431
+ for (const key in obj) {
432
+ const currentValue = obj[key];
433
+ if (key === '$ref' && typeof currentValue === 'string') {
434
+ normalized[key] = normalizeRef(currentValue);
435
+ } else {
436
+ normalized[key] = normalizeSchemaRefs(currentValue);
437
+ }
438
+ }
439
+
440
+ return normalized as T;
441
+ }
442
+
443
+ return value;
420
444
  }
421
445
 
422
- function normalizeSchemaRefs<T>(value: T): T {
446
+ function stripNestedSchemaTitles<T>(value: T, depth: number = 0): T {
423
447
  if (Array.isArray(value)) {
424
- return value.map(item => normalizeSchemaRefs(item)) as T;
448
+ return value.map(item => stripNestedSchemaTitles(item, depth + 1)) as T;
425
449
  }
426
450
 
427
451
  if (value && typeof value === 'object') {
428
452
  const obj = value as Record<string, unknown>;
429
453
  const normalized: Record<string, unknown> = {};
430
454
 
431
- for (const key in obj) {
432
- const currentValue = obj[key];
433
- if (key === '$ref' && typeof currentValue === 'string') {
434
- normalized[key] = normalizeRef(currentValue);
435
- } else {
436
- normalized[key] = normalizeSchemaRefs(currentValue);
455
+ for (const [key, currentValue] of Object.entries(obj)) {
456
+ // json-schema-to-typescript may generate invalid type aliases from nested title values
457
+ // (e.g. Chinese punctuation or numeric-leading titles), so keep only root-level titles.
458
+ if (key === 'title' && depth > 0) {
459
+ continue;
437
460
  }
461
+ normalized[key] = stripNestedSchemaTitles(currentValue, depth + 1);
438
462
  }
439
463
 
440
464
  return normalized as T;
@@ -442,8 +466,8 @@ function normalizeSchemaRefs<T>(value: T): T {
442
466
 
443
467
  return value;
444
468
  }
445
-
446
- interface IConfig {
469
+
470
+ interface IConfig {
447
471
  url: string;
448
472
  outputDir: string;
449
473
  httpClientPath: string;
@@ -564,14 +588,16 @@ export const handleBuild = async () => {
564
588
  if (Object.keys(module.schemas).length > 0) {
565
589
  console.log(chalk.blue(`正在为模块 "${moduleName}" 生成类型文件,包含 ${Object.keys(module.schemas).length} 个 schema`));
566
590
  try {
567
- const normalizedModuleSchemas = normalizeSchemaRefs(module.schemas);
568
- const schemasWithTitles: { [key: string]: OpenAPIV3.SchemaObject } = {};
569
- for (const schemaName in normalizedModuleSchemas) {
570
- schemasWithTitles[schemaName] = {
571
- ...normalizedModuleSchemas[schemaName],
572
- title: schemaName,
573
- };
574
- }
591
+ const normalizedModuleSchemas = stripNestedSchemaTitles(
592
+ normalizeSchemaRefs(module.schemas)
593
+ );
594
+ const schemasWithTitles: { [key: string]: OpenAPIV3.SchemaObject } = {};
595
+ for (const schemaName in normalizedModuleSchemas) {
596
+ schemasWithTitles[schemaName] = {
597
+ ...normalizedModuleSchemas[schemaName],
598
+ title: schemaName,
599
+ };
600
+ }
575
601
 
576
602
  const rootSchemaForCompiler = {
577
603
  title: 'schemas',
@@ -622,10 +648,12 @@ export const handleBuild = async () => {
622
648
 
623
649
  // 尝试逐个验证 schema,过滤掉有问题的
624
650
  const validSchemas: { [key: string]: OpenAPIV3.SchemaObject } = {};
625
- const invalidSchemas: string[] = [];
626
- const normalizedModuleSchemas = normalizeSchemaRefs(module.schemas);
651
+ const invalidSchemas: string[] = [];
652
+ const normalizedModuleSchemas = stripNestedSchemaTitles(
653
+ normalizeSchemaRefs(module.schemas)
654
+ );
627
655
 
628
- for (const schemaName in normalizedModuleSchemas) {
656
+ for (const schemaName in normalizedModuleSchemas) {
629
657
  try {
630
658
  // 尝试单独编译每个 schema
631
659
  const testSchema = {
@@ -633,12 +661,12 @@ export const handleBuild = async () => {
633
661
  type: 'object',
634
662
  properties: {},
635
663
  additionalProperties: false,
636
- definitions: { [schemaName]: normalizedModuleSchemas[schemaName] },
637
- components: { schemas: { [schemaName]: normalizedModuleSchemas[schemaName] } }
664
+ definitions: { [schemaName]: normalizedModuleSchemas[schemaName] },
665
+ components: { schemas: { [schemaName]: normalizedModuleSchemas[schemaName] } }
638
666
  };
639
667
  await compile(testSchema as any, 'test', { unreachableDefinitions: true });
640
668
  validSchemas[schemaName] = {
641
- ...normalizedModuleSchemas[schemaName],
669
+ ...normalizedModuleSchemas[schemaName],
642
670
  title: schemaName
643
671
  };
644
672
  } catch (schemaError) {
@@ -712,8 +740,8 @@ export const handleBuild = async () => {
712
740
  console.log(chalk.blue(`为模块 "${moduleName}" 生成基于 JSDoc 的类型定义...`));
713
741
 
714
742
  // 创建类型定义映射
715
- const typeDefinitions: { [typeName: string]: string } = {};
716
- const nestedReferencedTypes = new Set<string>();
743
+ const typeDefinitions: { [typeName: string]: string } = {};
744
+ const nestedReferencedTypes = new Set<string>();
717
745
 
718
746
  // 从 endpoints 中提取类型定义
719
747
  module.endpoints.forEach(endpoint => {
@@ -724,88 +752,88 @@ export const handleBuild = async () => {
724
752
  const optional = !param.required ? '?' : '';
725
753
  let type = param.type;
726
754
 
727
- if (type && type.includes(' | ')) {
728
- if (type.includes('null')) {
729
- type = type.replace(/\s*\|\s*null/g, ' | null');
730
- }
731
- } else {
732
- type = convertOpenApiTypeToTypeScript(type);
733
- }
734
-
735
- extractTypeIdentifiers(type).forEach(typeName => {
736
- if (isValidTypeIdentifier(typeName)) {
737
- nestedReferencedTypes.add(typeName);
738
- }
739
- });
740
-
741
- const comment = param.description ? ` // ${param.description}` : '';
742
- return ` ${param.name}${optional}: ${type};${comment}`;
743
- })
755
+ if (type && type.includes(' | ')) {
756
+ if (type.includes('null')) {
757
+ type = type.replace(/\s*\|\s*null/g, ' | null');
758
+ }
759
+ } else {
760
+ type = convertOpenApiTypeToTypeScript(type);
761
+ }
762
+
763
+ extractTypeIdentifiers(type).forEach(typeName => {
764
+ if (isValidTypeIdentifier(typeName)) {
765
+ nestedReferencedTypes.add(typeName);
766
+ }
767
+ });
768
+
769
+ const comment = param.description ? ` // ${param.description}` : '';
770
+ return ` ${param.name}${optional}: ${type};${comment}`;
771
+ })
744
772
  .join('\n');
745
773
 
746
774
  // 处理 params 类型 - 使用 paramsJsdocParams
747
- const requestParamsTypeName = endpoint.requestParamsTypeName;
748
- if (requestParamsTypeName && isValidTypeIdentifier(requestParamsTypeName) && endpoint.paramsJsdocParams && endpoint.paramsJsdocParams.length > 0) {
749
- const paramsFields = buildFields(endpoint.paramsJsdocParams);
750
-
751
- if (paramsFields) {
752
- typeDefinitions[requestParamsTypeName] = `export interface ${requestParamsTypeName} {
753
- ${paramsFields}
754
- }`;
755
- }
756
- }
775
+ const requestParamsTypeName = endpoint.requestParamsTypeName;
776
+ if (requestParamsTypeName && isValidTypeIdentifier(requestParamsTypeName) && endpoint.paramsJsdocParams && endpoint.paramsJsdocParams.length > 0) {
777
+ const paramsFields = buildFields(endpoint.paramsJsdocParams);
778
+
779
+ if (paramsFields) {
780
+ typeDefinitions[requestParamsTypeName] = `export interface ${requestParamsTypeName} {
781
+ ${paramsFields}
782
+ }`;
783
+ }
784
+ }
757
785
 
758
786
  // 处理 data 类型 (requestBody) - 使用 dataJsdocParams
759
- const requestBodyTypeName = endpoint.requestBodyTypeName;
760
- if (requestBodyTypeName && isValidTypeIdentifier(requestBodyTypeName) && endpoint.dataJsdocParams && endpoint.dataJsdocParams.length > 0) {
761
- const dataFields = buildFields(endpoint.dataJsdocParams);
762
-
763
- if (dataFields) {
764
- typeDefinitions[requestBodyTypeName] = `export interface ${requestBodyTypeName} {
765
- ${dataFields}
766
- }`;
767
- }
768
- }
787
+ const requestBodyTypeName = endpoint.requestBodyTypeName;
788
+ if (requestBodyTypeName && isValidTypeIdentifier(requestBodyTypeName) && endpoint.dataJsdocParams && endpoint.dataJsdocParams.length > 0) {
789
+ const dataFields = buildFields(endpoint.dataJsdocParams);
790
+
791
+ if (dataFields) {
792
+ typeDefinitions[requestBodyTypeName] = `export interface ${requestBodyTypeName} {
793
+ ${dataFields}
794
+ }`;
795
+ }
796
+ }
769
797
 
770
798
  // 处理 response 类型 - 使用 responseJsdocParams
771
- const responseTargetType = resolveInterfaceTargetType(endpoint.responseTypeName);
772
- if (responseTargetType && endpoint.responseTypeName !== 'void' && endpoint.responseJsdocParams && endpoint.responseJsdocParams.length > 0) {
773
- const responseFields = buildFields(endpoint.responseJsdocParams);
774
-
775
- if (responseFields) {
776
- typeDefinitions[responseTargetType] = `export interface ${responseTargetType} {
777
- ${responseFields}
778
- }`;
779
- }
780
- }
799
+ const responseTargetType = resolveInterfaceTargetType(endpoint.responseTypeName);
800
+ if (responseTargetType && endpoint.responseTypeName !== 'void' && endpoint.responseJsdocParams && endpoint.responseJsdocParams.length > 0) {
801
+ const responseFields = buildFields(endpoint.responseJsdocParams);
802
+
803
+ if (responseFields) {
804
+ typeDefinitions[responseTargetType] = `export interface ${responseTargetType} {
805
+ ${responseFields}
806
+ }`;
807
+ }
808
+ }
781
809
  });
782
810
 
783
811
  // 为没有定义的类型添加基本定义
784
- const fallbackReferencedTypes = [
785
- ...new Set([
786
- ...allReferencedTypes,
787
- ...Object.keys(module.schemas || {}),
788
- ...nestedReferencedTypes,
789
- ])
790
- ];
791
- fallbackReferencedTypes.forEach(typeName => {
792
- if (!isValidTypeIdentifier(typeName)) {
793
- return;
794
- }
795
- if (!typeDefinitions[typeName]) {
796
- const schemaBasedDefinition = buildInterfaceDefinitionFromModuleSchemas(
797
- typeName,
798
- module.schemas as { [typeName: string]: OpenAPIV3.SchemaObject }
799
- );
800
- if (schemaBasedDefinition) {
801
- typeDefinitions[typeName] = schemaBasedDefinition;
802
- return;
803
- }
804
- typeDefinitions[typeName] = `export interface ${typeName} {
805
- [key: string]: any;
806
- }`;
807
- }
808
- });
812
+ const fallbackReferencedTypes = [
813
+ ...new Set([
814
+ ...allReferencedTypes,
815
+ ...Object.keys(module.schemas || {}),
816
+ ...nestedReferencedTypes,
817
+ ])
818
+ ];
819
+ fallbackReferencedTypes.forEach(typeName => {
820
+ if (!isValidTypeIdentifier(typeName)) {
821
+ return;
822
+ }
823
+ if (!typeDefinitions[typeName]) {
824
+ const schemaBasedDefinition = buildInterfaceDefinitionFromModuleSchemas(
825
+ typeName,
826
+ module.schemas as { [typeName: string]: OpenAPIV3.SchemaObject }
827
+ );
828
+ if (schemaBasedDefinition) {
829
+ typeDefinitions[typeName] = schemaBasedDefinition;
830
+ return;
831
+ }
832
+ typeDefinitions[typeName] = `export interface ${typeName} {
833
+ [key: string]: any;
834
+ }`;
835
+ }
836
+ });
809
837
 
810
838
  const basicTypesContent = `/* eslint-disable */
811
839
  /**
@@ -879,4 +907,4 @@ ${Object.values(typeDefinitions).join('\n\n')}
879
907
  } catch (error) {
880
908
  console.error(chalk.red('An error occurred during build process:'), error);
881
909
  }
882
- };
910
+ };