@acrool/rtk-query-codegen-openapi 0.0.2-test.7 → 0.0.2-test.9
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 +1 -1
- package/lib/index.d.ts +1 -1
- package/lib/index.js +188 -30
- package/lib/index.js.map +1 -1
- package/lib/index.mjs +188 -30
- package/lib/index.mjs.map +1 -1
- package/package.json +1 -1
- package/src/generate.ts +203 -37
- package/src/index.ts +51 -20
- package/src/types.ts +1 -1
package/src/generate.ts
CHANGED
|
@@ -147,23 +147,24 @@ export async function generateApi(
|
|
|
147
147
|
const printer = ts.createPrinter({ newLine: ts.NewLineKind.LineFeed });
|
|
148
148
|
|
|
149
149
|
const allTypeDefinitions: ts.Statement[] = [];
|
|
150
|
+
const definedTypeNames = new Set<string>();
|
|
150
151
|
|
|
151
152
|
const components = v3Doc.components;
|
|
152
153
|
if (components) {
|
|
153
154
|
const componentDefinitions = Object.entries(components).map(([componentType, componentDefs]) => {
|
|
154
|
-
const typeEntries = Object.entries(componentDefs as Record<string, unknown>)
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
155
|
+
const typeEntries = Object.entries(componentDefs as Record<string, unknown>)
|
|
156
|
+
.map(([name, def]) => {
|
|
157
|
+
addSchemeTypeName(name);
|
|
158
|
+
const typeName = capitalize(camelCase(name));
|
|
159
|
+
definedTypeNames.add(typeName);
|
|
160
|
+
const typeNode = wrapWithSchemeIfComponent(apiGen.getTypeFromSchema(def as OpenAPIV3.SchemaObject));
|
|
161
|
+
return factory.createTypeAliasDeclaration(
|
|
162
|
+
[factory.createModifier(ts.SyntaxKind.ExportKeyword)],
|
|
163
|
+
factory.createIdentifier(typeName),
|
|
164
|
+
undefined,
|
|
165
|
+
typeNode
|
|
166
|
+
);
|
|
167
|
+
});
|
|
167
168
|
return factory.createModuleDeclaration(
|
|
168
169
|
[factory.createModifier(ts.SyntaxKind.ExportKeyword)],
|
|
169
170
|
factory.createIdentifier('Scheme'),
|
|
@@ -174,12 +175,116 @@ export async function generateApi(
|
|
|
174
175
|
allTypeDefinitions.push(...componentDefinitions);
|
|
175
176
|
}
|
|
176
177
|
|
|
177
|
-
|
|
178
|
-
|
|
178
|
+
const enumEntries = [
|
|
179
|
+
...apiGen.enumAliases.filter(e => ts.isEnumDeclaration(e)),
|
|
180
|
+
...apiGen.enumAliases.filter(e => ts.isTypeAliasDeclaration(e)),
|
|
181
|
+
].map(enumDecl => {
|
|
182
|
+
if (ts.isEnumDeclaration(enumDecl)) {
|
|
183
|
+
return factory.createEnumDeclaration(
|
|
184
|
+
[factory.createModifier(ts.SyntaxKind.ExportKeyword)],
|
|
185
|
+
enumDecl.name,
|
|
186
|
+
enumDecl.members
|
|
187
|
+
);
|
|
188
|
+
} else if (ts.isTypeAliasDeclaration(enumDecl)) {
|
|
189
|
+
return factory.createTypeAliasDeclaration(
|
|
190
|
+
[factory.createModifier(ts.SyntaxKind.ExportKeyword)],
|
|
191
|
+
enumDecl.name,
|
|
192
|
+
enumDecl.typeParameters,
|
|
193
|
+
enumDecl.type
|
|
194
|
+
);
|
|
195
|
+
}
|
|
196
|
+
return enumDecl;
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
const unionTypeEnums = apiGen.aliases
|
|
200
|
+
.filter(alias => {
|
|
201
|
+
if (ts.isTypeAliasDeclaration(alias) && alias.type) {
|
|
202
|
+
return ts.isUnionTypeNode(alias.type);
|
|
203
|
+
}
|
|
204
|
+
return false;
|
|
205
|
+
})
|
|
206
|
+
.map(alias => {
|
|
207
|
+
if (ts.isTypeAliasDeclaration(alias)) {
|
|
208
|
+
return factory.createTypeAliasDeclaration(
|
|
209
|
+
[factory.createModifier(ts.SyntaxKind.ExportKeyword)],
|
|
210
|
+
alias.name,
|
|
211
|
+
alias.typeParameters,
|
|
212
|
+
alias.type
|
|
213
|
+
);
|
|
214
|
+
}
|
|
215
|
+
return alias;
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
const allEnumEntries = [...enumEntries, ...unionTypeEnums];
|
|
219
|
+
|
|
220
|
+
if (allEnumEntries.length > 0) {
|
|
221
|
+
allTypeDefinitions.push(
|
|
222
|
+
factory.createModuleDeclaration(
|
|
223
|
+
[factory.createModifier(ts.SyntaxKind.ExportKeyword)],
|
|
224
|
+
factory.createIdentifier('Enum'),
|
|
225
|
+
factory.createModuleBlock(allEnumEntries),
|
|
226
|
+
ts.NodeFlags.Namespace
|
|
227
|
+
)
|
|
228
|
+
);
|
|
179
229
|
}
|
|
180
230
|
|
|
181
|
-
|
|
231
|
+
if (apiGen.aliases.length > 0) {
|
|
232
|
+
const aliasEntries = apiGen.aliases
|
|
233
|
+
.filter(alias => {
|
|
234
|
+
if (ts.isTypeAliasDeclaration(alias)) {
|
|
235
|
+
const isDefinedInComponents = definedTypeNames.has(alias.name.text);
|
|
236
|
+
const isUnionTypeEnum = ts.isUnionTypeNode(alias.type);
|
|
237
|
+
return !isDefinedInComponents && !isUnionTypeEnum;
|
|
238
|
+
}
|
|
239
|
+
return false;
|
|
240
|
+
})
|
|
241
|
+
.map(alias => {
|
|
242
|
+
if (ts.isTypeAliasDeclaration(alias)) {
|
|
243
|
+
return factory.createTypeAliasDeclaration(
|
|
244
|
+
[factory.createModifier(ts.SyntaxKind.ExportKeyword)],
|
|
245
|
+
alias.name,
|
|
246
|
+
alias.typeParameters,
|
|
247
|
+
alias.type
|
|
248
|
+
);
|
|
249
|
+
}
|
|
250
|
+
return alias;
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
if (aliasEntries.length > 0) {
|
|
254
|
+
const existingSchemeIndex = allTypeDefinitions.findIndex(def =>
|
|
255
|
+
ts.isModuleDeclaration(def) &&
|
|
256
|
+
ts.isIdentifier(def.name) &&
|
|
257
|
+
def.name.text === 'Scheme'
|
|
258
|
+
);
|
|
259
|
+
|
|
260
|
+
if (existingSchemeIndex >= 0) {
|
|
261
|
+
const existingScheme = allTypeDefinitions[existingSchemeIndex] as ts.ModuleDeclaration;
|
|
262
|
+
const mergedMembers = [...(existingScheme.body as ts.ModuleBlock).statements, ...aliasEntries];
|
|
263
|
+
allTypeDefinitions[existingSchemeIndex] = factory.createModuleDeclaration(
|
|
264
|
+
[factory.createModifier(ts.SyntaxKind.ExportKeyword)],
|
|
265
|
+
factory.createIdentifier('Scheme'),
|
|
266
|
+
factory.createModuleBlock(mergedMembers),
|
|
267
|
+
ts.NodeFlags.Namespace
|
|
268
|
+
);
|
|
269
|
+
} else {
|
|
270
|
+
allTypeDefinitions.push(
|
|
271
|
+
factory.createModuleDeclaration(
|
|
272
|
+
[factory.createModifier(ts.SyntaxKind.ExportKeyword)],
|
|
273
|
+
factory.createIdentifier('Scheme'),
|
|
274
|
+
factory.createModuleBlock(aliasEntries),
|
|
275
|
+
ts.NodeFlags.Namespace
|
|
276
|
+
)
|
|
277
|
+
);
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
}
|
|
182
281
|
|
|
282
|
+
const fs = await import('node:fs/promises');
|
|
283
|
+
const path = await import('node:path');
|
|
284
|
+
|
|
285
|
+
const sharedTypesDir = path.dirname(sharedTypesFile);
|
|
286
|
+
await fs.mkdir(sharedTypesDir, { recursive: true });
|
|
287
|
+
|
|
183
288
|
const output = printer.printNode(
|
|
184
289
|
ts.EmitHint.Unspecified,
|
|
185
290
|
factory.createSourceFile(
|
|
@@ -190,7 +295,6 @@ export async function generateApi(
|
|
|
190
295
|
resultFile
|
|
191
296
|
);
|
|
192
297
|
|
|
193
|
-
const fs = await import('node:fs/promises');
|
|
194
298
|
await fs.writeFile(sharedTypesFile, output, 'utf-8');
|
|
195
299
|
}
|
|
196
300
|
|
|
@@ -245,7 +349,12 @@ export async function generateApi(
|
|
|
245
349
|
[
|
|
246
350
|
generateImportNode(apiFile, { [apiImport]: 'api' }),
|
|
247
351
|
generateImportNode('@acrool/react-fetcher', { IRestFulEndpointsQueryReturn: 'IRestFulEndpointsQueryReturn' }),
|
|
248
|
-
...(sharedTypesFile ? [
|
|
352
|
+
...(sharedTypesFile ? [
|
|
353
|
+
generateImportNode(sharedTypesImportPath, {
|
|
354
|
+
Scheme: 'Scheme',
|
|
355
|
+
...(useEnumType ? { Enum: 'Enum' } : {})
|
|
356
|
+
})
|
|
357
|
+
] : []),
|
|
249
358
|
...(tag ? [generateTagTypes({ addTagTypes: extractAllTagTypes({ operationDefinitions }) })] : []),
|
|
250
359
|
generateCreateApiCall({
|
|
251
360
|
tag,
|
|
@@ -336,25 +445,6 @@ export async function generateApi(
|
|
|
336
445
|
)
|
|
337
446
|
.filter(([_1, _2, type]) => type !== keywordType.void)
|
|
338
447
|
.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
448
|
return ts.addSyntheticLeadingComment(
|
|
359
449
|
type,
|
|
360
450
|
ts.SyntaxKind.MultiLineCommentTrivia,
|
|
@@ -620,6 +710,36 @@ export async function generateApi(
|
|
|
620
710
|
function wrapWithSchemeIfComponent(typeNode: ts.TypeNode): ts.TypeNode {
|
|
621
711
|
if (ts.isTypeReferenceNode(typeNode) && ts.isIdentifier(typeNode.typeName)) {
|
|
622
712
|
const typeName = typeNode.typeName.text;
|
|
713
|
+
|
|
714
|
+
// 檢查是否為 enum 類型(包括在 enumAliases 和 aliases 中的)
|
|
715
|
+
const isEnumType = useEnumType && (
|
|
716
|
+
apiGen.enumAliases.some(enumDecl => {
|
|
717
|
+
if (ts.isEnumDeclaration(enumDecl) || ts.isTypeAliasDeclaration(enumDecl)) {
|
|
718
|
+
return enumDecl.name.text === typeName;
|
|
719
|
+
}
|
|
720
|
+
return false;
|
|
721
|
+
}) ||
|
|
722
|
+
apiGen.aliases.some(alias => {
|
|
723
|
+
if (ts.isTypeAliasDeclaration(alias) && alias.type) {
|
|
724
|
+
// 檢查是否為 union type 的 enum
|
|
725
|
+
if (ts.isUnionTypeNode(alias.type)) {
|
|
726
|
+
return alias.name.text === typeName;
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
return false;
|
|
730
|
+
})
|
|
731
|
+
);
|
|
732
|
+
|
|
733
|
+
if (isEnumType) {
|
|
734
|
+
return factory.createTypeReferenceNode(
|
|
735
|
+
factory.createQualifiedName(
|
|
736
|
+
factory.createIdentifier('Enum'),
|
|
737
|
+
typeNode.typeName
|
|
738
|
+
),
|
|
739
|
+
typeNode.typeArguments?.map(wrapWithSchemeIfComponent)
|
|
740
|
+
);
|
|
741
|
+
}
|
|
742
|
+
|
|
623
743
|
if (schemeTypeNames.has(typeName)) {
|
|
624
744
|
return factory.createTypeReferenceNode(
|
|
625
745
|
factory.createQualifiedName(
|
|
@@ -640,6 +760,52 @@ export async function generateApi(
|
|
|
640
760
|
return factory.createArrayTypeNode(wrapWithSchemeIfComponent(typeNode.elementType));
|
|
641
761
|
}
|
|
642
762
|
if (ts.isUnionTypeNode(typeNode)) {
|
|
763
|
+
// 檢查是否為 enum 的 union type
|
|
764
|
+
const unionTypes = typeNode.types;
|
|
765
|
+
if (unionTypes.length > 0 && unionTypes.every(type =>
|
|
766
|
+
ts.isLiteralTypeNode(type) &&
|
|
767
|
+
(ts.isStringLiteral(type.literal) || ts.isNumericLiteral(type.literal))
|
|
768
|
+
)) {
|
|
769
|
+
// 這是一個 enum 的 union type,我們需要找到對應的 enum 類型
|
|
770
|
+
const enumValues = unionTypes.map(type => {
|
|
771
|
+
if (ts.isLiteralTypeNode(type)) {
|
|
772
|
+
if (ts.isStringLiteral(type.literal)) {
|
|
773
|
+
return type.literal.text;
|
|
774
|
+
} else if (ts.isNumericLiteral(type.literal)) {
|
|
775
|
+
return type.literal.text;
|
|
776
|
+
}
|
|
777
|
+
}
|
|
778
|
+
return null;
|
|
779
|
+
}).filter(Boolean);
|
|
780
|
+
|
|
781
|
+
// 查找對應的 enum 類型
|
|
782
|
+
const matchingEnum = apiGen.aliases.find(alias => {
|
|
783
|
+
if (ts.isTypeAliasDeclaration(alias) && ts.isUnionTypeNode(alias.type)) {
|
|
784
|
+
const aliasValues = alias.type.types.map(type => {
|
|
785
|
+
if (ts.isLiteralTypeNode(type)) {
|
|
786
|
+
if (ts.isStringLiteral(type.literal)) {
|
|
787
|
+
return type.literal.text;
|
|
788
|
+
} else if (ts.isNumericLiteral(type.literal)) {
|
|
789
|
+
return type.literal.text;
|
|
790
|
+
}
|
|
791
|
+
}
|
|
792
|
+
return null;
|
|
793
|
+
}).filter(Boolean);
|
|
794
|
+
|
|
795
|
+
return aliasValues.length === enumValues.length &&
|
|
796
|
+
aliasValues.every(val => enumValues.includes(val));
|
|
797
|
+
}
|
|
798
|
+
return false;
|
|
799
|
+
});
|
|
800
|
+
|
|
801
|
+
// 對於所有的 enum 類型,直接使用字串型別,不轉換為 Enum
|
|
802
|
+
// 這樣可以避免自動命名造成的變更問題
|
|
803
|
+
if (matchingEnum && ts.isTypeAliasDeclaration(matchingEnum)) {
|
|
804
|
+
// 直接返回原始的 union type,不轉換為 Enum
|
|
805
|
+
return typeNode;
|
|
806
|
+
}
|
|
807
|
+
}
|
|
808
|
+
|
|
643
809
|
return factory.createUnionTypeNode(typeNode.types.map(wrapWithSchemeIfComponent));
|
|
644
810
|
}
|
|
645
811
|
if (ts.isTypeLiteralNode(typeNode)) {
|
package/src/index.ts
CHANGED
|
@@ -121,37 +121,68 @@ export function parseConfig(fullConfig: ConfigFile) {
|
|
|
121
121
|
const openApiDoc = JSON.parse(fs.readFileSync(fullConfig.schemaFile, 'utf-8'));
|
|
122
122
|
const paths = Object.keys(openApiDoc.paths);
|
|
123
123
|
|
|
124
|
-
|
|
125
124
|
// 從配置中獲取分類規則
|
|
126
125
|
const [outputPath, config] = Object.entries(outputFiles)[0];
|
|
127
126
|
const patterns = config.groupMatch;
|
|
128
|
-
|
|
129
127
|
const filterEndpoint = config.filterEndpoint;
|
|
130
|
-
|
|
131
128
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
129
|
+
const pattern = patterns;
|
|
130
|
+
// 根據路徑自動分類
|
|
131
|
+
const groupedPaths = paths.reduce((acc, path) => {
|
|
132
|
+
const groupName = getGroupNameFromPath(path, pattern);
|
|
133
|
+
if (!acc[groupName]) {
|
|
134
|
+
acc[groupName] = [];
|
|
135
|
+
}
|
|
136
|
+
acc[groupName].push(path);
|
|
137
|
+
return acc;
|
|
138
|
+
}, {} as Record<string, string[]>);
|
|
139
|
+
|
|
140
|
+
// 為每個分類生成配置
|
|
141
|
+
Object.entries(groupedPaths).forEach(([groupName, paths]) => {
|
|
142
|
+
const finalOutputPath = outputPath.replace('$1', groupName);
|
|
143
|
+
|
|
144
|
+
if (filterEndpoint) {
|
|
145
|
+
// 如果有 filterEndpoint,使用基於路徑的篩選函數
|
|
146
|
+
const pathBasedFilter = (operationName: string, operationDefinition: any) => {
|
|
147
|
+
const path = operationDefinition.path;
|
|
148
|
+
|
|
149
|
+
// 檢查路徑是否匹配當前分組
|
|
150
|
+
const pathGroupName = getGroupNameFromPath(path, pattern);
|
|
151
|
+
if (pathGroupName !== groupName) {
|
|
152
|
+
return false;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// 使用 filterEndpoint 進行額外篩選
|
|
156
|
+
const endpointFilter = filterEndpoint(groupName);
|
|
157
|
+
if (endpointFilter instanceof RegExp) {
|
|
158
|
+
return endpointFilter.test(operationName);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
return true;
|
|
162
|
+
};
|
|
135
163
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
164
|
+
outFiles.push({
|
|
165
|
+
...commonConfig,
|
|
166
|
+
outputFile: finalOutputPath,
|
|
167
|
+
filterEndpoints: pathBasedFilter,
|
|
168
|
+
});
|
|
169
|
+
} else {
|
|
170
|
+
// 如果沒有 filterEndpoint,只使用路徑分組
|
|
171
|
+
const pathBasedFilter = (operationName: string, operationDefinition: any) => {
|
|
172
|
+
const path = operationDefinition.path;
|
|
173
|
+
|
|
174
|
+
// 檢查路徑是否匹配當前分組
|
|
175
|
+
const pathGroupName = getGroupNameFromPath(path, pattern);
|
|
176
|
+
return pathGroupName === groupName;
|
|
177
|
+
};
|
|
147
178
|
|
|
148
|
-
const filterEndpoints = filterEndpoint(groupName);
|
|
149
179
|
outFiles.push({
|
|
150
180
|
...commonConfig,
|
|
151
181
|
outputFile: finalOutputPath,
|
|
152
|
-
filterEndpoints:
|
|
182
|
+
filterEndpoints: pathBasedFilter,
|
|
153
183
|
});
|
|
154
|
-
}
|
|
184
|
+
}
|
|
185
|
+
});
|
|
155
186
|
|
|
156
187
|
} else {
|
|
157
188
|
outFiles.push(fullConfig);
|
package/src/types.ts
CHANGED