czh-api 1.0.3 → 1.0.5
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 -5
- package/README.md +4 -11
- package/dist/commands/build.js +320 -107
- package/dist/core/parser.js +134 -16
- package/dist/templates/api.hbs +1 -6
- package/package.json +1 -1
- package/src/commands/build.ts +427 -159
- package/src/core/parser.ts +170 -34
- package/src/templates/api.hbs +1 -6
package/src/core/parser.ts
CHANGED
|
@@ -16,6 +16,11 @@ export interface Endpoint {
|
|
|
16
16
|
hasData: boolean;
|
|
17
17
|
contentType?: string;
|
|
18
18
|
jsdocParams: Array<{ name: string; type?: string; description?: string; required?: boolean }>;
|
|
19
|
+
// Separate jsdoc params for params and data to avoid merging
|
|
20
|
+
paramsJsdocParams: Array<{ name: string; type?: string; description?: string; required?: boolean }>;
|
|
21
|
+
dataJsdocParams: Array<{ name: string; type?: string; description?: string; required?: boolean }>;
|
|
22
|
+
// Jsdoc params for response type
|
|
23
|
+
responseJsdocParams: Array<{ name: string; type?: string; description?: string; required?: boolean }>;
|
|
19
24
|
}
|
|
20
25
|
|
|
21
26
|
export interface Module {
|
|
@@ -106,7 +111,7 @@ function getModuleName(path: string, pathPrefixes: Array<{ path: string; package
|
|
|
106
111
|
return parts[0] || 'default';
|
|
107
112
|
}
|
|
108
113
|
|
|
109
|
-
function convertOpenApiTypeToTypeScript(openApiType: string | undefined): string {
|
|
114
|
+
function convertOpenApiTypeToTypeScript(openApiType: string | undefined): string {
|
|
110
115
|
if (!openApiType) return 'any';
|
|
111
116
|
|
|
112
117
|
switch (openApiType) {
|
|
@@ -120,12 +125,88 @@ function convertOpenApiTypeToTypeScript(openApiType: string | undefined): string
|
|
|
120
125
|
return 'boolean';
|
|
121
126
|
case 'string':
|
|
122
127
|
return 'string';
|
|
123
|
-
case 'number':
|
|
124
|
-
return 'number';
|
|
125
|
-
default:
|
|
126
|
-
return
|
|
127
|
-
}
|
|
128
|
-
}
|
|
128
|
+
case 'number':
|
|
129
|
+
return 'number';
|
|
130
|
+
default:
|
|
131
|
+
return openApiType;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function resolveSchemaToTypeScript(
|
|
136
|
+
schema: OpenAPIV3.SchemaObject | OpenAPIV3.ReferenceObject | undefined,
|
|
137
|
+
allSchemas: { [key: string]: any }
|
|
138
|
+
): string {
|
|
139
|
+
if (!schema) return 'any';
|
|
140
|
+
|
|
141
|
+
if (isReferenceObject(schema)) {
|
|
142
|
+
return getSchemaName(schema.$ref) || 'any';
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
if (schema.anyOf && Array.isArray(schema.anyOf) && schema.anyOf.length > 0) {
|
|
146
|
+
const unionTypes = schema.anyOf
|
|
147
|
+
.map(item => resolveSchemaToTypeScript(item as OpenAPIV3.SchemaObject | OpenAPIV3.ReferenceObject, allSchemas))
|
|
148
|
+
.filter(Boolean);
|
|
149
|
+
if (unionTypes.length > 0) {
|
|
150
|
+
return [...new Set(unionTypes)].join(' | ');
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
if (schema.oneOf && Array.isArray(schema.oneOf) && schema.oneOf.length > 0) {
|
|
155
|
+
const unionTypes = schema.oneOf
|
|
156
|
+
.map(item => resolveSchemaToTypeScript(item as OpenAPIV3.SchemaObject | OpenAPIV3.ReferenceObject, allSchemas))
|
|
157
|
+
.filter(Boolean);
|
|
158
|
+
if (unionTypes.length > 0) {
|
|
159
|
+
return [...new Set(unionTypes)].join(' | ');
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
if (schema.allOf && Array.isArray(schema.allOf) && schema.allOf.length > 0) {
|
|
164
|
+
const intersectionTypes = schema.allOf
|
|
165
|
+
.map(item => resolveSchemaToTypeScript(item as OpenAPIV3.SchemaObject | OpenAPIV3.ReferenceObject, allSchemas))
|
|
166
|
+
.filter(Boolean);
|
|
167
|
+
if (intersectionTypes.length > 0) {
|
|
168
|
+
return [...new Set(intersectionTypes)].join(' & ');
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
if (schema.type === 'array') {
|
|
173
|
+
const itemType = resolveSchemaToTypeScript(
|
|
174
|
+
schema.items as OpenAPIV3.SchemaObject | OpenAPIV3.ReferenceObject | undefined,
|
|
175
|
+
allSchemas
|
|
176
|
+
);
|
|
177
|
+
return `${itemType || 'any'}[]`;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
if (schema.type === 'object') {
|
|
181
|
+
if (schema.properties && Object.keys(schema.properties).length > 0) {
|
|
182
|
+
const requiredSet = new Set(schema.required || []);
|
|
183
|
+
const inlineFields = Object.entries(schema.properties).map(([key, value]) => {
|
|
184
|
+
const fieldType = resolveSchemaToTypeScript(
|
|
185
|
+
value as OpenAPIV3.SchemaObject | OpenAPIV3.ReferenceObject,
|
|
186
|
+
allSchemas
|
|
187
|
+
);
|
|
188
|
+
const optional = requiredSet.has(key) ? '' : '?';
|
|
189
|
+
return `${key}${optional}: ${fieldType || 'any'}`;
|
|
190
|
+
});
|
|
191
|
+
return `{ ${inlineFields.join('; ')} }`;
|
|
192
|
+
}
|
|
193
|
+
if (schema.additionalProperties) {
|
|
194
|
+
if (schema.additionalProperties === true) {
|
|
195
|
+
return 'Record<string, any>';
|
|
196
|
+
}
|
|
197
|
+
const valueType = resolveSchemaToTypeScript(
|
|
198
|
+
schema.additionalProperties as OpenAPIV3.SchemaObject | OpenAPIV3.ReferenceObject,
|
|
199
|
+
allSchemas
|
|
200
|
+
);
|
|
201
|
+
return `Record<string, ${valueType || 'any'}>`;
|
|
202
|
+
}
|
|
203
|
+
return 'any';
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
const baseType = convertOpenApiTypeToTypeScript(schema.type);
|
|
207
|
+
const nullable = schema.nullable ? ' | null' : '';
|
|
208
|
+
return `${baseType}${nullable}`;
|
|
209
|
+
}
|
|
129
210
|
|
|
130
211
|
function extractJsdocParamsFromSchema(schema: OpenAPIV3.SchemaObject | OpenAPIV3.ReferenceObject, allSchemas: { [key: string]: any }): Required<Endpoint>['jsdocParams'] {
|
|
131
212
|
const params: Required<Endpoint>['jsdocParams'] = [];
|
|
@@ -141,13 +222,15 @@ function extractJsdocParamsFromSchema(schema: OpenAPIV3.SchemaObject | OpenAPIV3
|
|
|
141
222
|
|
|
142
223
|
if (targetSchema?.properties) {
|
|
143
224
|
for (const propName in targetSchema.properties) {
|
|
144
|
-
const prop = targetSchema.properties[propName] as OpenAPIV3.SchemaObject;
|
|
145
|
-
|
|
146
|
-
|
|
225
|
+
const prop = targetSchema.properties[propName] as OpenAPIV3.SchemaObject | OpenAPIV3.ReferenceObject;
|
|
226
|
+
let propType = resolveSchemaToTypeScript(prop, allSchemas);
|
|
227
|
+
const propDescription = isReferenceObject(prop)
|
|
228
|
+
? ((allSchemas[getSchemaName(prop.$ref)] as OpenAPIV3.SchemaObject | undefined)?.description || '')
|
|
229
|
+
: (prop?.description || '');
|
|
147
230
|
|
|
148
231
|
// 处理 anyOf 数组 (FastAPI 常用的联合类型)
|
|
149
|
-
if (prop.anyOf && Array.isArray(prop.anyOf)) {
|
|
150
|
-
const types = prop.anyOf
|
|
232
|
+
if (false && (prop as OpenAPIV3.SchemaObject).anyOf && Array.isArray((prop as OpenAPIV3.SchemaObject).anyOf)) {
|
|
233
|
+
const types = (prop as OpenAPIV3.SchemaObject).anyOf!.map(item => {
|
|
151
234
|
if (isReferenceObject(item)) {
|
|
152
235
|
return getSchemaName(item.$ref);
|
|
153
236
|
} else {
|
|
@@ -168,8 +251,8 @@ function extractJsdocParamsFromSchema(schema: OpenAPIV3.SchemaObject | OpenAPIV3
|
|
|
168
251
|
}
|
|
169
252
|
}
|
|
170
253
|
// 处理 oneOf 数组
|
|
171
|
-
else if (prop.oneOf && Array.isArray(prop.oneOf)) {
|
|
172
|
-
const types = prop.oneOf
|
|
254
|
+
else if (false && (prop as OpenAPIV3.SchemaObject).oneOf && Array.isArray((prop as OpenAPIV3.SchemaObject).oneOf)) {
|
|
255
|
+
const types = (prop as OpenAPIV3.SchemaObject).oneOf!.map(item => {
|
|
173
256
|
if (isReferenceObject(item)) {
|
|
174
257
|
return getSchemaName(item.$ref);
|
|
175
258
|
} else {
|
|
@@ -190,16 +273,26 @@ function extractJsdocParamsFromSchema(schema: OpenAPIV3.SchemaObject | OpenAPIV3
|
|
|
190
273
|
}
|
|
191
274
|
}
|
|
192
275
|
// 处理普通类型
|
|
193
|
-
else if (prop.type) {
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
276
|
+
else if (false && (prop as OpenAPIV3.SchemaObject).type) {
|
|
277
|
+
const propSchema: any = prop as any;
|
|
278
|
+
if (propSchema.type === 'array' && propSchema.items) {
|
|
279
|
+
if (isReferenceObject(propSchema.items)) {
|
|
280
|
+
propType = `${getSchemaName(propSchema.items.$ref)}[]`;
|
|
281
|
+
} else {
|
|
282
|
+
const itemType = convertOpenApiTypeToTypeScript((propSchema.items as OpenAPIV3.SchemaObject).type);
|
|
283
|
+
propType = `${itemType}[]`;
|
|
284
|
+
}
|
|
285
|
+
} else {
|
|
286
|
+
propType = convertOpenApiTypeToTypeScript(propSchema.type);
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
params.push({
|
|
291
|
+
name: propName,
|
|
292
|
+
type: propType || 'any',
|
|
293
|
+
description: propDescription,
|
|
294
|
+
required: targetSchema.required?.includes(propName)
|
|
295
|
+
});
|
|
203
296
|
}
|
|
204
297
|
}
|
|
205
298
|
return params;
|
|
@@ -290,6 +383,9 @@ export const processApi = (api: OpenAPI.Document, excludePaths: string[] = [], i
|
|
|
290
383
|
let requestBodyTypeName: string | undefined = undefined;
|
|
291
384
|
let contentType: string | undefined = undefined;
|
|
292
385
|
const jsdocParams: Required<Endpoint>['jsdocParams'] = [];
|
|
386
|
+
const paramsJsdocParams: Required<Endpoint>['paramsJsdocParams'] = [];
|
|
387
|
+
const dataJsdocParams: Required<Endpoint>['dataJsdocParams'] = [];
|
|
388
|
+
const responseJsdocParams: Required<Endpoint>['responseJsdocParams'] = [];
|
|
293
389
|
|
|
294
390
|
// --- FormData Detection ---
|
|
295
391
|
let isFormData = false;
|
|
@@ -378,14 +474,18 @@ export const processApi = (api: OpenAPI.Document, excludePaths: string[] = [], i
|
|
|
378
474
|
requestBodyTypeName = typeName;
|
|
379
475
|
modules[moduleName].schemas[typeName] = formDataSchema;
|
|
380
476
|
referencedTypes.push(typeName);
|
|
381
|
-
|
|
477
|
+
const formDataJsdoc = extractJsdocParamsFromSchema(formDataSchema, allSchemas);
|
|
478
|
+
jsdocParams.push(...formDataJsdoc);
|
|
479
|
+
dataJsdocParams.push(...formDataJsdoc);
|
|
382
480
|
}
|
|
383
481
|
|
|
384
482
|
if (Object.keys(paramsSchema.properties || {}).length > 0) {
|
|
385
483
|
requestParamsTypeName = `${functionName.charAt(0).toUpperCase() + functionName.slice(1)}Params`;
|
|
386
484
|
modules[moduleName].schemas[requestParamsTypeName] = paramsSchema;
|
|
387
485
|
referencedTypes.push(requestParamsTypeName);
|
|
388
|
-
|
|
486
|
+
const paramsJsdoc = extractJsdocParamsFromSchema(paramsSchema, allSchemas);
|
|
487
|
+
jsdocParams.push(...paramsJsdoc);
|
|
488
|
+
paramsJsdocParams.push(...paramsJsdoc);
|
|
389
489
|
}
|
|
390
490
|
}
|
|
391
491
|
|
|
@@ -400,7 +500,9 @@ export const processApi = (api: OpenAPI.Document, excludePaths: string[] = [], i
|
|
|
400
500
|
requestBodyTypeName = name;
|
|
401
501
|
addSchemaWithDependencies(name, modules[moduleName], allSchemas);
|
|
402
502
|
referencedTypes.push(name);
|
|
403
|
-
|
|
503
|
+
const bodyJsdoc = extractJsdocParamsFromSchema(jsonContent.schema, allSchemas);
|
|
504
|
+
jsdocParams.push(...bodyJsdoc);
|
|
505
|
+
dataJsdocParams.push(...bodyJsdoc);
|
|
404
506
|
} else {
|
|
405
507
|
// Handle inline schema (including arrays)
|
|
406
508
|
const schema = jsonContent.schema as OpenAPIV3.SchemaObject;
|
|
@@ -416,10 +518,14 @@ export const processApi = (api: OpenAPI.Document, excludePaths: string[] = [], i
|
|
|
416
518
|
const itemSchemaName = getSchemaName(schema.items.$ref);
|
|
417
519
|
addSchemaWithDependencies(itemSchemaName, modules[moduleName], allSchemas);
|
|
418
520
|
} else {
|
|
419
|
-
|
|
521
|
+
const arrayJsdoc = extractJsdocParamsFromSchema(schema.items as OpenAPIV3.SchemaObject, allSchemas);
|
|
522
|
+
jsdocParams.push(...arrayJsdoc);
|
|
523
|
+
dataJsdocParams.push(...arrayJsdoc);
|
|
420
524
|
}
|
|
421
525
|
} else if (schema.type === 'object') {
|
|
422
|
-
|
|
526
|
+
const objJsdoc = extractJsdocParamsFromSchema(schema, allSchemas);
|
|
527
|
+
jsdocParams.push(...objJsdoc);
|
|
528
|
+
dataJsdocParams.push(...objJsdoc);
|
|
423
529
|
}
|
|
424
530
|
}
|
|
425
531
|
}
|
|
@@ -431,8 +537,9 @@ export const processApi = (api: OpenAPI.Document, excludePaths: string[] = [], i
|
|
|
431
537
|
const typeName = `${functionName.charAt(0).toUpperCase() + functionName.slice(1)}Data`;
|
|
432
538
|
requestBodyTypeName = typeName;
|
|
433
539
|
modules[moduleName].schemas[typeName] = formDataContent.schema as OpenAPIV3.SchemaObject;
|
|
434
|
-
|
|
435
|
-
|
|
540
|
+
referencedTypes.push(typeName);
|
|
541
|
+
const formJsdoc = extractJsdocParamsFromSchema(formDataContent.schema, allSchemas);
|
|
542
|
+
dataJsdocParams.push(...formJsdoc);
|
|
436
543
|
}
|
|
437
544
|
}
|
|
438
545
|
|
|
@@ -448,8 +555,34 @@ export const processApi = (api: OpenAPI.Document, excludePaths: string[] = [], i
|
|
|
448
555
|
responseTypeName = name;
|
|
449
556
|
addSchemaWithDependencies(name, modules[moduleName], allSchemas);
|
|
450
557
|
referencedTypes.push(name);
|
|
451
|
-
|
|
452
|
-
|
|
558
|
+
responseJsdocParams.push(...extractJsdocParamsFromSchema(schema, allSchemas));
|
|
559
|
+
} else if (schema && !isReferenceObject(schema)) {
|
|
560
|
+
const inlineSchema = schema as OpenAPIV3.SchemaObject;
|
|
561
|
+
if (inlineSchema.type === 'object' || inlineSchema.properties) {
|
|
562
|
+
// Generate a named type for inline response schema
|
|
563
|
+
const typeName = `${functionName.charAt(0).toUpperCase() + functionName.slice(1)}Response`;
|
|
564
|
+
responseTypeName = typeName;
|
|
565
|
+
modules[moduleName].schemas[typeName] = inlineSchema;
|
|
566
|
+
referencedTypes.push(typeName);
|
|
567
|
+
responseJsdocParams.push(...extractJsdocParamsFromSchema(inlineSchema, allSchemas));
|
|
568
|
+
} else if (inlineSchema.type === 'array' && inlineSchema.items) {
|
|
569
|
+
if (isReferenceObject(inlineSchema.items)) {
|
|
570
|
+
const itemSchemaName = getSchemaName(inlineSchema.items.$ref);
|
|
571
|
+
// Prefer direct `ItemType[]` for array responses to avoid unnecessary alias schema compilation.
|
|
572
|
+
responseTypeName = `${itemSchemaName}[]`;
|
|
573
|
+
referencedTypes.push(itemSchemaName);
|
|
574
|
+
addSchemaWithDependencies(itemSchemaName, modules[moduleName], allSchemas);
|
|
575
|
+
responseJsdocParams.push(...extractJsdocParamsFromSchema(inlineSchema.items, allSchemas));
|
|
576
|
+
} else {
|
|
577
|
+
const typeName = `${functionName.charAt(0).toUpperCase() + functionName.slice(1)}Response`;
|
|
578
|
+
responseTypeName = typeName;
|
|
579
|
+
modules[moduleName].schemas[typeName] = inlineSchema;
|
|
580
|
+
referencedTypes.push(typeName);
|
|
581
|
+
responseJsdocParams.push(...extractJsdocParamsFromSchema(inlineSchema, allSchemas));
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
}
|
|
453
586
|
|
|
454
587
|
// Convert path to template literal string for path parameters
|
|
455
588
|
const urlTemplate = path.replace(/\{(\w+)\}/g, '${params.$1}');
|
|
@@ -467,6 +600,9 @@ export const processApi = (api: OpenAPI.Document, excludePaths: string[] = [], i
|
|
|
467
600
|
hasData: !!requestBodyTypeName,
|
|
468
601
|
contentType,
|
|
469
602
|
jsdocParams,
|
|
603
|
+
paramsJsdocParams,
|
|
604
|
+
dataJsdocParams,
|
|
605
|
+
responseJsdocParams,
|
|
470
606
|
};
|
|
471
607
|
|
|
472
608
|
modules[moduleName].endpoints.push(endpoint);
|
|
@@ -474,4 +610,4 @@ export const processApi = (api: OpenAPI.Document, excludePaths: string[] = [], i
|
|
|
474
610
|
}
|
|
475
611
|
|
|
476
612
|
return modules;
|
|
477
|
-
};
|
|
613
|
+
};
|
package/src/templates/api.hbs
CHANGED
|
@@ -10,12 +10,7 @@ export const {{functionName}} = ({{#if hasParams}}params: {{requestParamsTypeNam
|
|
|
10
10
|
return http.request<{{responseTypeName}}>({
|
|
11
11
|
url: `{{path}}`,
|
|
12
12
|
method: '{{method}}',
|
|
13
|
-
{{#if hasParams}}
|
|
14
|
-
params,
|
|
15
|
-
{{/if}}
|
|
16
|
-
{{#if hasData}}
|
|
17
|
-
data,
|
|
18
|
-
{{/if}}
|
|
13
|
+
{{#if hasParams}}{{#unless (eq method 'put')}}{{#unless (eq method 'post')}}params,{{/unless}}{{/unless}}{{/if}}{{#if hasData}}data,{{/if}}
|
|
19
14
|
{{#if contentType}}
|
|
20
15
|
headers: { 'Content-Type': '{{contentType}}' },
|
|
21
16
|
{{/if}}
|