@acrool/rtk-query-codegen-openapi 0.0.2-test.6 → 0.0.2-test.7
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/lib/index.d.mts +8 -4
- package/lib/index.d.ts +8 -4
- package/lib/index.js +219 -113
- package/lib/index.js.map +1 -1
- package/lib/index.mjs +219 -113
- package/lib/index.mjs.map +1 -1
- package/package.json +1 -1
- package/src/generate.ts +162 -128
- package/src/generators/react-hooks.ts +2 -2
- package/src/index.ts +120 -8
- package/src/types.ts +9 -1
package/src/generate.ts
CHANGED
|
@@ -38,8 +38,8 @@ function defaultIsDataResponse(code: string, includeDefault: boolean) {
|
|
|
38
38
|
return !Number.isNaN(parsedCode) && parsedCode >= 200 && parsedCode < 300;
|
|
39
39
|
}
|
|
40
40
|
|
|
41
|
-
function getOperationName({ verb, path
|
|
42
|
-
return _getOperationName(verb, path,
|
|
41
|
+
function getOperationName({ verb, path }: Pick<OperationDefinition, 'verb' | 'path' >) {
|
|
42
|
+
return _getOperationName(verb, path, undefined);
|
|
43
43
|
}
|
|
44
44
|
|
|
45
45
|
function getTags({ verb, pathItem }: Pick<OperationDefinition, 'verb' | 'pathItem'>): string[] {
|
|
@@ -128,49 +128,72 @@ export async function generateApi(
|
|
|
128
128
|
mergeReadWriteOnly,
|
|
129
129
|
});
|
|
130
130
|
|
|
131
|
-
|
|
131
|
+
const schemeTypeNames = new Set<string>();
|
|
132
|
+
|
|
133
|
+
function addSchemeTypeName(name: string) {
|
|
134
|
+
schemeTypeNames.add(name);
|
|
135
|
+
schemeTypeNames.add(camelCase(name));
|
|
136
|
+
schemeTypeNames.add(capitalize(camelCase(name)));
|
|
137
|
+
}
|
|
138
|
+
|
|
132
139
|
if (sharedTypesFile) {
|
|
140
|
+
const resultFile = ts.createSourceFile(
|
|
141
|
+
'sharedTypes.ts',
|
|
142
|
+
'',
|
|
143
|
+
ts.ScriptTarget.Latest,
|
|
144
|
+
/*setParentNodes*/ false,
|
|
145
|
+
ts.ScriptKind.TS
|
|
146
|
+
);
|
|
147
|
+
const printer = ts.createPrinter({ newLine: ts.NewLineKind.LineFeed });
|
|
148
|
+
|
|
149
|
+
const allTypeDefinitions: ts.Statement[] = [];
|
|
150
|
+
|
|
133
151
|
const components = v3Doc.components;
|
|
134
152
|
if (components) {
|
|
135
|
-
const
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
);
|
|
142
|
-
const printer = ts.createPrinter({ newLine: ts.NewLineKind.LineFeed });
|
|
143
|
-
|
|
144
|
-
// 將 components 轉換為 TypeScript 類型定義
|
|
145
|
-
const typeDefinitions = Object.entries(components).flatMap(([_, componentDefs]) => {
|
|
146
|
-
return Object.entries(componentDefs as Record<string, unknown>).map(([name, def]) => {
|
|
147
|
-
const typeNode = apiGen.getTypeFromSchema(def as OpenAPIV3.SchemaObject);
|
|
153
|
+
const componentDefinitions = Object.entries(components).map(([componentType, componentDefs]) => {
|
|
154
|
+
const typeEntries = Object.entries(componentDefs as Record<string, unknown>).map(([name, def]) => {
|
|
155
|
+
addSchemeTypeName(name);
|
|
156
|
+
|
|
157
|
+
const typeName = capitalize(camelCase(name));
|
|
158
|
+
const typeNode = wrapWithSchemeIfComponent(apiGen.getTypeFromSchema(def as OpenAPIV3.SchemaObject));
|
|
148
159
|
return factory.createTypeAliasDeclaration(
|
|
149
160
|
[factory.createModifier(ts.SyntaxKind.ExportKeyword)],
|
|
150
|
-
factory.createIdentifier(
|
|
161
|
+
factory.createIdentifier(typeName),
|
|
151
162
|
undefined,
|
|
152
163
|
typeNode
|
|
153
164
|
);
|
|
154
165
|
});
|
|
155
|
-
});
|
|
156
166
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
167
|
+
return factory.createModuleDeclaration(
|
|
168
|
+
[factory.createModifier(ts.SyntaxKind.ExportKeyword)],
|
|
169
|
+
factory.createIdentifier('Scheme'),
|
|
170
|
+
factory.createModuleBlock(typeEntries),
|
|
171
|
+
ts.NodeFlags.Namespace
|
|
172
|
+
);
|
|
173
|
+
});
|
|
174
|
+
allTypeDefinitions.push(...componentDefinitions);
|
|
175
|
+
}
|
|
166
176
|
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
await fs.writeFile(sharedTypesFile, output, 'utf-8');
|
|
177
|
+
if (useEnumType) {
|
|
178
|
+
allTypeDefinitions.push(...apiGen.enumAliases);
|
|
170
179
|
}
|
|
180
|
+
|
|
181
|
+
allTypeDefinitions.push(...apiGen.aliases);
|
|
182
|
+
|
|
183
|
+
const output = printer.printNode(
|
|
184
|
+
ts.EmitHint.Unspecified,
|
|
185
|
+
factory.createSourceFile(
|
|
186
|
+
allTypeDefinitions,
|
|
187
|
+
factory.createToken(ts.SyntaxKind.EndOfFileToken),
|
|
188
|
+
ts.NodeFlags.None
|
|
189
|
+
),
|
|
190
|
+
resultFile
|
|
191
|
+
);
|
|
192
|
+
|
|
193
|
+
const fs = await import('node:fs/promises');
|
|
194
|
+
await fs.writeFile(sharedTypesFile, output, 'utf-8');
|
|
171
195
|
}
|
|
172
196
|
|
|
173
|
-
// temporary workaround for https://github.com/oazapfts/oazapfts/issues/491
|
|
174
197
|
if (apiGen.spec.components?.schemas) {
|
|
175
198
|
apiGen.preprocessComponents(apiGen.spec.components.schemas);
|
|
176
199
|
}
|
|
@@ -203,17 +226,18 @@ export async function generateApi(
|
|
|
203
226
|
apiFile = apiFile.replace(/\\/g, '/');
|
|
204
227
|
if (!apiFile.startsWith('.')) apiFile = `./${apiFile}`;
|
|
205
228
|
}
|
|
206
|
-
// 處理 sharedTypesFile 的路徑
|
|
207
|
-
if (sharedTypesFile && sharedTypesFile.startsWith('.')) {
|
|
208
|
-
sharedTypesFile = path.relative(path.dirname(outputFile), sharedTypesFile);
|
|
209
|
-
sharedTypesFile = sharedTypesFile.replace(/\\/g, '/');
|
|
210
|
-
if (!sharedTypesFile.startsWith('.')) sharedTypesFile = `./${sharedTypesFile}`;
|
|
211
|
-
}
|
|
212
229
|
}
|
|
213
230
|
apiFile = apiFile.replace(/\.[jt]sx?$/, '');
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
231
|
+
|
|
232
|
+
const sharedTypesImportPath = sharedTypesFile && outputFile
|
|
233
|
+
? (() => {
|
|
234
|
+
let rel = path.relative(path.dirname(outputFile), sharedTypesFile)
|
|
235
|
+
.replace(/\\/g, '/')
|
|
236
|
+
.replace(/\.[jt]sx?$/, '');
|
|
237
|
+
if (!rel.startsWith('.')) rel = './' + rel;
|
|
238
|
+
return rel;
|
|
239
|
+
})()
|
|
240
|
+
: './shared-types';
|
|
217
241
|
|
|
218
242
|
return printer.printNode(
|
|
219
243
|
ts.EmitHint.Unspecified,
|
|
@@ -221,18 +245,7 @@ export async function generateApi(
|
|
|
221
245
|
[
|
|
222
246
|
generateImportNode(apiFile, { [apiImport]: 'api' }),
|
|
223
247
|
generateImportNode('@acrool/react-fetcher', { IRestFulEndpointsQueryReturn: 'IRestFulEndpointsQueryReturn' }),
|
|
224
|
-
...(sharedTypesFile ? [
|
|
225
|
-
factory.createImportDeclaration(
|
|
226
|
-
undefined,
|
|
227
|
-
factory.createImportClause(
|
|
228
|
-
false,
|
|
229
|
-
undefined,
|
|
230
|
-
factory.createNamespaceImport(factory.createIdentifier('SharedTypes'))
|
|
231
|
-
),
|
|
232
|
-
factory.createStringLiteral(sharedTypesFile),
|
|
233
|
-
undefined
|
|
234
|
-
)
|
|
235
|
-
] : []),
|
|
248
|
+
...(sharedTypesFile ? [generateImportNode(sharedTypesImportPath, { Scheme: 'Scheme' })] : []),
|
|
236
249
|
...(tag ? [generateTagTypes({ addTagTypes: extractAllTagTypes({ operationDefinitions }) })] : []),
|
|
237
250
|
generateCreateApiCall({
|
|
238
251
|
tag,
|
|
@@ -241,6 +254,7 @@ export async function generateApi(
|
|
|
241
254
|
generateEndpoint({
|
|
242
255
|
operationDefinition,
|
|
243
256
|
overrides: getOverrides(operationDefinition, endpointOverrides),
|
|
257
|
+
sharedTypesFile: !!sharedTypesFile,
|
|
244
258
|
})
|
|
245
259
|
),
|
|
246
260
|
true
|
|
@@ -285,9 +299,11 @@ export async function generateApi(
|
|
|
285
299
|
function generateEndpoint({
|
|
286
300
|
operationDefinition,
|
|
287
301
|
overrides,
|
|
302
|
+
sharedTypesFile,
|
|
288
303
|
}: {
|
|
289
304
|
operationDefinition: OperationDefinition;
|
|
290
305
|
overrides?: EndpointOverrides;
|
|
306
|
+
sharedTypesFile: boolean;
|
|
291
307
|
}) {
|
|
292
308
|
const {
|
|
293
309
|
verb,
|
|
@@ -296,73 +312,56 @@ export async function generateApi(
|
|
|
296
312
|
operation,
|
|
297
313
|
operation: { responses, requestBody },
|
|
298
314
|
} = operationDefinition;
|
|
299
|
-
const operationName = getOperationName({ verb, path
|
|
315
|
+
const operationName = getOperationName({ verb, path });
|
|
300
316
|
const tags = tag ? getTags({ verb, pathItem }) : [];
|
|
301
317
|
const isQuery = testIsQuery(verb, overrides);
|
|
302
318
|
|
|
303
319
|
const returnsJson = apiGen.getResponseType(responses) === 'json';
|
|
304
320
|
let ResponseType: ts.TypeNode = factory.createKeywordTypeNode(ts.SyntaxKind.UnknownKeyword);
|
|
305
|
-
|
|
306
|
-
function replaceReferences(schema: any): ts.TypeNode {
|
|
307
|
-
if (!schema) return factory.createKeywordTypeNode(ts.SyntaxKind.UnknownKeyword);
|
|
308
|
-
|
|
309
|
-
const refName = getReferenceName(schema);
|
|
310
|
-
if (refName && sharedTypesFile) {
|
|
311
|
-
return factory.createTypeReferenceNode(
|
|
312
|
-
factory.createQualifiedName(
|
|
313
|
-
factory.createIdentifier('SharedTypes'),
|
|
314
|
-
factory.createIdentifier(refName)
|
|
315
|
-
),
|
|
316
|
-
undefined
|
|
317
|
-
);
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
if (schema.type === 'object' && schema.properties) {
|
|
321
|
-
const members = Object.entries(schema.properties).map(([key, value]: [string, any]) => {
|
|
322
|
-
return factory.createPropertySignature(
|
|
323
|
-
undefined,
|
|
324
|
-
factory.createIdentifier(key),
|
|
325
|
-
schema.required?.includes(key) ? undefined : factory.createToken(ts.SyntaxKind.QuestionToken),
|
|
326
|
-
replaceReferences(value)
|
|
327
|
-
);
|
|
328
|
-
});
|
|
329
|
-
return factory.createTypeLiteralNode(members);
|
|
330
|
-
}
|
|
331
|
-
|
|
332
|
-
if (schema.type === 'array' && schema.items) {
|
|
333
|
-
return factory.createArrayTypeNode(replaceReferences(schema.items));
|
|
334
|
-
}
|
|
335
|
-
|
|
336
|
-
return apiGen.getTypeFromSchema(schema);
|
|
337
|
-
}
|
|
338
|
-
|
|
339
321
|
if (returnsJson) {
|
|
340
322
|
const returnTypes = Object.entries(responses || {})
|
|
341
323
|
.map(
|
|
342
|
-
([code, response]) =>
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
return [code, resolvedResponse, type] as const;
|
|
352
|
-
}
|
|
324
|
+
([code, response]) =>
|
|
325
|
+
[
|
|
326
|
+
code,
|
|
327
|
+
apiGen.resolve(response),
|
|
328
|
+
wrapWithSchemeIfComponent(
|
|
329
|
+
apiGen.getTypeFromResponse(response, 'readOnly') ||
|
|
330
|
+
factory.createKeywordTypeNode(ts.SyntaxKind.UndefinedKeyword)
|
|
331
|
+
),
|
|
332
|
+
] as const
|
|
353
333
|
)
|
|
354
334
|
.filter(([status, response]) =>
|
|
355
335
|
isDataResponse(status, includeDefault, apiGen.resolve(response), responses || {})
|
|
356
336
|
)
|
|
357
337
|
.filter(([_1, _2, type]) => type !== keywordType.void)
|
|
358
|
-
.map(([code, response, type]) =>
|
|
359
|
-
ts.
|
|
360
|
-
|
|
338
|
+
.map(([code, response, type]) => {
|
|
339
|
+
if (sharedTypesFile && ts.isTypeReferenceNode(type) && type.typeName) {
|
|
340
|
+
if (ts.isIdentifier(type.typeName)) {
|
|
341
|
+
const typeName = type.typeName.text;
|
|
342
|
+
if (typeName in apiGen.aliases || typeName in apiGen.enumAliases) {
|
|
343
|
+
return ts.addSyntheticLeadingComment(
|
|
344
|
+
factory.createTypeReferenceNode(
|
|
345
|
+
factory.createQualifiedName(
|
|
346
|
+
factory.createIdentifier('sharedTypes'),
|
|
347
|
+
factory.createIdentifier(camelCase(typeName))
|
|
348
|
+
),
|
|
349
|
+
type.typeArguments
|
|
350
|
+
),
|
|
351
|
+
ts.SyntaxKind.MultiLineCommentTrivia,
|
|
352
|
+
`* status ${code} ${response.description} `,
|
|
353
|
+
false
|
|
354
|
+
);
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
return ts.addSyntheticLeadingComment(
|
|
359
|
+
type,
|
|
361
360
|
ts.SyntaxKind.MultiLineCommentTrivia,
|
|
362
361
|
`* status ${code} ${response.description} `,
|
|
363
362
|
false
|
|
364
|
-
)
|
|
365
|
-
);
|
|
363
|
+
);
|
|
364
|
+
});
|
|
366
365
|
if (returnTypes.length > 0) {
|
|
367
366
|
ResponseType = factory.createUnionTypeNode(returnTypes);
|
|
368
367
|
}
|
|
@@ -392,28 +391,36 @@ export async function generateApi(
|
|
|
392
391
|
const queryArg: QueryArgDefinitions = {};
|
|
393
392
|
function generateName(name: string, potentialPrefix: string) {
|
|
394
393
|
const isPureSnakeCase = /^[a-zA-Z][a-zA-Z0-9_]*$/.test(name);
|
|
395
|
-
// prefix with `query`, `path` or `body` if there are multiple paramters with the same name
|
|
396
394
|
const hasNamingConflict = allNames.filter((n) => n === name).length > 1;
|
|
397
395
|
if (hasNamingConflict) {
|
|
398
396
|
name = `${potentialPrefix}_${name}`;
|
|
399
397
|
}
|
|
400
|
-
// convert to camelCase if the name is pure snake_case and there are no naming conflicts
|
|
401
398
|
const camelCaseName = camelCase(name);
|
|
402
399
|
if (isPureSnakeCase && !allNames.includes(camelCaseName)) {
|
|
403
400
|
name = camelCaseName;
|
|
404
401
|
}
|
|
405
|
-
// if there are still any naming conflicts, prepend with underscore
|
|
406
402
|
while (name in queryArg) {
|
|
407
403
|
name = `_${name}`;
|
|
408
404
|
}
|
|
409
405
|
return name;
|
|
410
406
|
}
|
|
411
407
|
|
|
408
|
+
for (const param of parameters) {
|
|
409
|
+
const name = generateName(param.name, param.in);
|
|
410
|
+
queryArg[name] = {
|
|
411
|
+
origin: 'param',
|
|
412
|
+
name,
|
|
413
|
+
originalName: param.name,
|
|
414
|
+
type: wrapWithSchemeIfComponent(apiGen.getTypeFromSchema(isReference(param) ? param : param.schema, undefined, 'writeOnly')),
|
|
415
|
+
required: param.required,
|
|
416
|
+
param,
|
|
417
|
+
};
|
|
418
|
+
}
|
|
419
|
+
|
|
412
420
|
if (requestBody) {
|
|
413
421
|
const body = apiGen.resolve(requestBody);
|
|
414
422
|
const schema = apiGen.getSchemaFromContent(body.content);
|
|
415
|
-
const type =
|
|
416
|
-
|
|
423
|
+
const type = wrapWithSchemeIfComponent(apiGen.getTypeFromSchema(schema));
|
|
417
424
|
const schemaName = camelCase(
|
|
418
425
|
(type as any).name ||
|
|
419
426
|
getReferenceName(schema) ||
|
|
@@ -426,27 +433,12 @@ export async function generateApi(
|
|
|
426
433
|
origin: 'body',
|
|
427
434
|
name,
|
|
428
435
|
originalName: schemaName,
|
|
429
|
-
type,
|
|
436
|
+
type: wrapWithSchemeIfComponent(apiGen.getTypeFromSchema(schema, undefined, 'writeOnly')),
|
|
430
437
|
required: true,
|
|
431
438
|
body,
|
|
432
439
|
};
|
|
433
440
|
}
|
|
434
441
|
|
|
435
|
-
for (const param of parameters) {
|
|
436
|
-
const name = generateName(param.name, param.in);
|
|
437
|
-
const paramSchema = isReference(param) ? param : param.schema;
|
|
438
|
-
const type = replaceReferences(paramSchema);
|
|
439
|
-
|
|
440
|
-
queryArg[name] = {
|
|
441
|
-
origin: 'param',
|
|
442
|
-
name,
|
|
443
|
-
originalName: param.name,
|
|
444
|
-
type,
|
|
445
|
-
required: param.required,
|
|
446
|
-
param,
|
|
447
|
-
};
|
|
448
|
-
}
|
|
449
|
-
|
|
450
442
|
const propertyName = (name: string | ts.PropertyName): ts.PropertyName => {
|
|
451
443
|
if (typeof name === 'string') {
|
|
452
444
|
return isValidIdentifier(name) ? factory.createIdentifier(name) : factory.createStringLiteral(name);
|
|
@@ -617,14 +609,56 @@ export async function generateApi(
|
|
|
617
609
|
);
|
|
618
610
|
}
|
|
619
611
|
|
|
620
|
-
// eslint-disable-next-line no-empty-pattern
|
|
621
612
|
function generateQueryEndpointProps({}: { operationDefinition: OperationDefinition }): ObjectPropertyDefinitions {
|
|
622
|
-
return {};
|
|
613
|
+
return {};
|
|
623
614
|
}
|
|
624
615
|
|
|
625
|
-
// eslint-disable-next-line no-empty-pattern
|
|
626
616
|
function generateMutationEndpointProps({}: { operationDefinition: OperationDefinition }): ObjectPropertyDefinitions {
|
|
627
|
-
return {};
|
|
617
|
+
return {};
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
function wrapWithSchemeIfComponent(typeNode: ts.TypeNode): ts.TypeNode {
|
|
621
|
+
if (ts.isTypeReferenceNode(typeNode) && ts.isIdentifier(typeNode.typeName)) {
|
|
622
|
+
const typeName = typeNode.typeName.text;
|
|
623
|
+
if (schemeTypeNames.has(typeName)) {
|
|
624
|
+
return factory.createTypeReferenceNode(
|
|
625
|
+
factory.createQualifiedName(
|
|
626
|
+
factory.createIdentifier('Scheme'),
|
|
627
|
+
typeNode.typeName
|
|
628
|
+
),
|
|
629
|
+
typeNode.typeArguments?.map(wrapWithSchemeIfComponent)
|
|
630
|
+
);
|
|
631
|
+
}
|
|
632
|
+
if (typeNode.typeArguments) {
|
|
633
|
+
return factory.createTypeReferenceNode(
|
|
634
|
+
typeNode.typeName,
|
|
635
|
+
typeNode.typeArguments.map(wrapWithSchemeIfComponent)
|
|
636
|
+
);
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
if (ts.isArrayTypeNode(typeNode)) {
|
|
640
|
+
return factory.createArrayTypeNode(wrapWithSchemeIfComponent(typeNode.elementType));
|
|
641
|
+
}
|
|
642
|
+
if (ts.isUnionTypeNode(typeNode)) {
|
|
643
|
+
return factory.createUnionTypeNode(typeNode.types.map(wrapWithSchemeIfComponent));
|
|
644
|
+
}
|
|
645
|
+
if (ts.isTypeLiteralNode(typeNode)) {
|
|
646
|
+
return factory.createTypeLiteralNode(
|
|
647
|
+
typeNode.members.map(member => {
|
|
648
|
+
if (ts.isPropertySignature(member) && member.type) {
|
|
649
|
+
return factory.updatePropertySignature(
|
|
650
|
+
member,
|
|
651
|
+
member.modifiers,
|
|
652
|
+
member.name,
|
|
653
|
+
member.questionToken,
|
|
654
|
+
wrapWithSchemeIfComponent(member.type)
|
|
655
|
+
);
|
|
656
|
+
}
|
|
657
|
+
return member;
|
|
658
|
+
})
|
|
659
|
+
);
|
|
660
|
+
}
|
|
661
|
+
return typeNode;
|
|
628
662
|
}
|
|
629
663
|
}
|
|
630
664
|
|
|
@@ -20,7 +20,7 @@ type CreateBindingParams = {
|
|
|
20
20
|
};
|
|
21
21
|
|
|
22
22
|
const createBinding = ({
|
|
23
|
-
operationDefinition: { verb, path
|
|
23
|
+
operationDefinition: { verb, path },
|
|
24
24
|
overrides,
|
|
25
25
|
isLazy = false,
|
|
26
26
|
}: CreateBindingParams) =>
|
|
@@ -28,7 +28,7 @@ const createBinding = ({
|
|
|
28
28
|
undefined,
|
|
29
29
|
undefined,
|
|
30
30
|
factory.createIdentifier(
|
|
31
|
-
`use${isLazy ? 'Lazy' : ''}${capitalize(getOperationName(verb, path,
|
|
31
|
+
`use${isLazy ? 'Lazy' : ''}${capitalize(getOperationName(verb, path, undefined))}${
|
|
32
32
|
isQuery(verb, overrides) ? 'Query' : 'Mutation'
|
|
33
33
|
}`
|
|
34
34
|
),
|
package/src/index.ts
CHANGED
|
@@ -4,10 +4,84 @@ import path from 'node:path';
|
|
|
4
4
|
import { generateApi } from './generate';
|
|
5
5
|
import type { CommonOptions, ConfigFile, GenerationOptions, OutputFileOptions } from './types';
|
|
6
6
|
import { isValidUrl, prettify } from './utils';
|
|
7
|
-
|
|
7
|
+
import camelCase from 'lodash.camelcase';
|
|
8
|
+
export type { OutputFilesConfig, ConfigFile } from './types';
|
|
8
9
|
|
|
9
10
|
const require = createRequire(__filename);
|
|
10
11
|
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
// 確保目錄存在的函數
|
|
15
|
+
async function ensureDirectoryExists(filePath: string) {
|
|
16
|
+
const dirname = path.dirname(filePath);
|
|
17
|
+
if (!fs.existsSync(dirname)) {
|
|
18
|
+
await fs.promises.mkdir(dirname, { recursive: true });
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
// 檢查檔案是否存在的函數
|
|
24
|
+
function fileExists(filePath: string): boolean {
|
|
25
|
+
try {
|
|
26
|
+
return fs.statSync(filePath).isFile();
|
|
27
|
+
} catch {
|
|
28
|
+
return false;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// 獲取資料夾名稱並轉換為 API 名稱
|
|
33
|
+
function getApiNameFromDir(dirPath: string): string {
|
|
34
|
+
const dirName = path.basename(dirPath);
|
|
35
|
+
return `${dirName}Api`;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// 確保基礎文件存在的函數
|
|
39
|
+
async function ensureBaseFilesExist(outputDir: string) {
|
|
40
|
+
const enhanceEndpointsPath = path.join(outputDir, 'enhanceEndpoints.ts');
|
|
41
|
+
const indexPath = path.join(outputDir, 'index.ts');
|
|
42
|
+
const apiName = getApiNameFromDir(outputDir);
|
|
43
|
+
|
|
44
|
+
// 如果 enhanceEndpoints.ts 不存在,創建它
|
|
45
|
+
if (!fileExists(enhanceEndpointsPath)) {
|
|
46
|
+
const enhanceEndpointsContent = `import api from './query.generated';
|
|
47
|
+
|
|
48
|
+
const enhancedApi = api.enhanceEndpoints({
|
|
49
|
+
endpoints: {
|
|
50
|
+
},
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
export default enhancedApi;
|
|
54
|
+
`;
|
|
55
|
+
await fs.promises.writeFile(enhanceEndpointsPath, enhanceEndpointsContent, 'utf-8');
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// 如果 index.ts 不存在,創建它
|
|
59
|
+
if (!fileExists(indexPath)) {
|
|
60
|
+
const indexContent = `export * from './query.generated';
|
|
61
|
+
export {default as ${apiName}} from './enhanceEndpoints';
|
|
62
|
+
`;
|
|
63
|
+
await fs.promises.writeFile(indexPath, indexContent, 'utf-8');
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
// 從路徑中提取分類名稱
|
|
69
|
+
function getGroupNameFromPath(path: string, pattern: RegExp): string {
|
|
70
|
+
// console.log('pattern', pattern);
|
|
71
|
+
|
|
72
|
+
const match = path.match(pattern);
|
|
73
|
+
// console.log('match', path, match);
|
|
74
|
+
|
|
75
|
+
if (match && match[1]) {
|
|
76
|
+
return camelCase(match[1]);
|
|
77
|
+
}
|
|
78
|
+
return 'common';
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
|
|
11
85
|
export async function generateEndpoints(options: GenerationOptions): Promise<string | void> {
|
|
12
86
|
const schemaLocation = options.schemaFile;
|
|
13
87
|
|
|
@@ -20,8 +94,15 @@ export async function generateEndpoints(options: GenerationOptions): Promise<str
|
|
|
20
94
|
});
|
|
21
95
|
const { outputFile, prettierConfigFile } = options;
|
|
22
96
|
if (outputFile) {
|
|
97
|
+
const outputPath = path.resolve(process.cwd(), outputFile);
|
|
98
|
+
await ensureDirectoryExists(outputPath);
|
|
99
|
+
|
|
100
|
+
// 確保基礎文件存在
|
|
101
|
+
const outputDir = path.dirname(outputPath);
|
|
102
|
+
await ensureBaseFilesExist(outputDir);
|
|
103
|
+
|
|
23
104
|
fs.writeFileSync(
|
|
24
|
-
|
|
105
|
+
outputPath,
|
|
25
106
|
await prettify(outputFile, sourceCode, prettierConfigFile)
|
|
26
107
|
);
|
|
27
108
|
} else {
|
|
@@ -29,18 +110,49 @@ export async function generateEndpoints(options: GenerationOptions): Promise<str
|
|
|
29
110
|
}
|
|
30
111
|
}
|
|
31
112
|
|
|
113
|
+
|
|
32
114
|
export function parseConfig(fullConfig: ConfigFile) {
|
|
33
115
|
const outFiles: (CommonOptions & OutputFileOptions)[] = [];
|
|
34
116
|
|
|
35
117
|
if ('outputFiles' in fullConfig) {
|
|
36
118
|
const { outputFiles, ...commonConfig } = fullConfig;
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
119
|
+
|
|
120
|
+
// 讀取 OpenAPI 文檔
|
|
121
|
+
const openApiDoc = JSON.parse(fs.readFileSync(fullConfig.schemaFile, 'utf-8'));
|
|
122
|
+
const paths = Object.keys(openApiDoc.paths);
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
// 從配置中獲取分類規則
|
|
126
|
+
const [outputPath, config] = Object.entries(outputFiles)[0];
|
|
127
|
+
const patterns = config.groupMatch;
|
|
128
|
+
|
|
129
|
+
const filterEndpoint = config.filterEndpoint;
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
const pattern = patterns;
|
|
133
|
+
// 根據路徑自動分類
|
|
134
|
+
const groupedPaths = paths.reduce((acc, path) => {
|
|
135
|
+
|
|
136
|
+
const groupName = getGroupNameFromPath(path, pattern);
|
|
137
|
+
if (!acc[groupName]) {
|
|
138
|
+
acc[groupName] = [];
|
|
139
|
+
}
|
|
140
|
+
acc[groupName].push(path);
|
|
141
|
+
return acc;
|
|
142
|
+
}, {} as Record<string, string[]>);
|
|
143
|
+
|
|
144
|
+
// 為每個分類生成配置
|
|
145
|
+
Object.entries(groupedPaths).forEach(([groupName, paths]) => {
|
|
146
|
+
const finalOutputPath = outputPath.replace('$1', groupName);
|
|
147
|
+
|
|
148
|
+
const filterEndpoints = filterEndpoint(groupName);
|
|
149
|
+
outFiles.push({
|
|
150
|
+
...commonConfig,
|
|
151
|
+
outputFile: finalOutputPath,
|
|
152
|
+
filterEndpoints: [filterEndpoints],
|
|
153
|
+
});
|
|
42
154
|
});
|
|
43
|
-
|
|
155
|
+
|
|
44
156
|
} else {
|
|
45
157
|
outFiles.push(fullConfig);
|
|
46
158
|
}
|
package/src/types.ts
CHANGED
|
@@ -142,10 +142,18 @@ export type EndpointOverrides = {
|
|
|
142
142
|
parameterFilter: ParameterMatcher;
|
|
143
143
|
}>;
|
|
144
144
|
|
|
145
|
+
export type OutputFilesConfig = {
|
|
146
|
+
[outputFile: string]: {
|
|
147
|
+
groupMatch: RegExp,
|
|
148
|
+
filterEndpoint: (groupName: string) => RegExp
|
|
149
|
+
}
|
|
150
|
+
};
|
|
151
|
+
|
|
145
152
|
export type ConfigFile =
|
|
146
153
|
| Id<Require<CommonOptions & OutputFileOptions, 'outputFile'>>
|
|
147
154
|
| Id<
|
|
148
155
|
Omit<CommonOptions, 'outputFile'> & {
|
|
149
|
-
outputFiles: { [outputFile: string]: Omit<OutputFileOptions, 'outputFile'> };
|
|
156
|
+
// outputFiles: { [outputFile: string]: Omit<OutputFileOptions, 'outputFile'> };
|
|
157
|
+
outputFiles: OutputFilesConfig
|
|
150
158
|
}
|
|
151
159
|
>;
|