@kubb/plugin-ts 5.0.0-beta.30 → 5.0.0-beta.33
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/dist/index.cjs +124 -61
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +124 -61
- package/dist/index.js.map +1 -1
- package/extension.yaml +2 -0
- package/package.json +6 -6
- package/src/generators/typeGenerator.tsx +58 -69
- package/src/plugin.ts +3 -13
- package/src/utils.ts +1 -1
package/dist/index.cjs
CHANGED
|
@@ -834,6 +834,54 @@ function Type({ name, node, printer, enumType, enumTypeSuffix, enumKeyCasing, re
|
|
|
834
834
|
}
|
|
835
835
|
//#endregion
|
|
836
836
|
//#region ../../internals/shared/src/operation.ts
|
|
837
|
+
/**
|
|
838
|
+
* Maps a content type to the PascalCase suffix used to name per-content-type variants
|
|
839
|
+
* (e.g. `application/json` → `Json`, `application/xml` → `Xml`, `multipart/form-data` → `FormData`).
|
|
840
|
+
*/
|
|
841
|
+
function getContentTypeSuffix(contentType) {
|
|
842
|
+
const baseType = contentType.split(";")[0].trim();
|
|
843
|
+
if (baseType === "application/json") return "Json";
|
|
844
|
+
if (baseType === "multipart/form-data") return "FormData";
|
|
845
|
+
if (baseType === "application/x-www-form-urlencoded") return "FormUrlEncoded";
|
|
846
|
+
const parts = (baseType.split("/").pop() ?? baseType).split(/[^a-zA-Z0-9]+/).filter(Boolean);
|
|
847
|
+
if (parts.length === 0) return "Unknown";
|
|
848
|
+
return parts.map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join("");
|
|
849
|
+
}
|
|
850
|
+
/**
|
|
851
|
+
* Appends a content-type suffix to a base name, keeping a trailing `Data` segment last
|
|
852
|
+
* (e.g. `AddPetData` + `Json` → `AddPetJsonData`, `AddPetStatus200` + `Xml` → `AddPetStatus200Xml`).
|
|
853
|
+
*/
|
|
854
|
+
function getPerContentTypeName(baseName, suffix) {
|
|
855
|
+
if (baseName.endsWith("Data")) return suffix.endsWith("Data") ? baseName.slice(0, -4) + suffix : `${baseName.slice(0, -4)}${suffix}Data`;
|
|
856
|
+
return baseName + suffix;
|
|
857
|
+
}
|
|
858
|
+
/**
|
|
859
|
+
* Resolves per-content-type variant names for a set of content entries, deduplicating suffix
|
|
860
|
+
* collisions with a numeric counter. Entries without a schema are skipped. The returned `suffix` is
|
|
861
|
+
* the final (possibly counter-augmented) value, so callers can derive parallel names in another
|
|
862
|
+
* namespace (e.g. plugin-faker deriving the matching plugin-ts type name).
|
|
863
|
+
*/
|
|
864
|
+
function resolveContentTypeVariants(entries, baseName) {
|
|
865
|
+
const usedNames = /* @__PURE__ */ new Set();
|
|
866
|
+
return entries.filter((entry) => entry.schema).map((entry) => {
|
|
867
|
+
const baseSuffix = getContentTypeSuffix(entry.contentType);
|
|
868
|
+
let suffix = baseSuffix;
|
|
869
|
+
let name = getPerContentTypeName(baseName, suffix);
|
|
870
|
+
let counter = 2;
|
|
871
|
+
while (usedNames.has(name)) {
|
|
872
|
+
suffix = `${baseSuffix}${counter++}`;
|
|
873
|
+
name = getPerContentTypeName(baseName, suffix);
|
|
874
|
+
}
|
|
875
|
+
usedNames.add(name);
|
|
876
|
+
return {
|
|
877
|
+
name,
|
|
878
|
+
suffix,
|
|
879
|
+
schema: entry.schema,
|
|
880
|
+
keysToOmit: entry.keysToOmit,
|
|
881
|
+
contentType: entry.contentType
|
|
882
|
+
};
|
|
883
|
+
});
|
|
884
|
+
}
|
|
837
885
|
function getOperationParameters(node, options = {}) {
|
|
838
886
|
const params = _kubb_core.ast.caseParams(node.parameters, options.paramsCasing);
|
|
839
887
|
return {
|
|
@@ -844,6 +892,39 @@ function getOperationParameters(node, options = {}) {
|
|
|
844
892
|
};
|
|
845
893
|
}
|
|
846
894
|
//#endregion
|
|
895
|
+
//#region ../../internals/shared/src/group.ts
|
|
896
|
+
/**
|
|
897
|
+
* Builds the `group` config a Kubb plugin passes to `ctx.setOptions`, applying the
|
|
898
|
+
* shared default naming so every plugin groups output consistently:
|
|
899
|
+
*
|
|
900
|
+
* - `path` groups use the second path segment (`/pet/findByStatus` → `pet`).
|
|
901
|
+
* - other groups use `${camelCase(group)}${suffix}` (e.g. `petController`).
|
|
902
|
+
*
|
|
903
|
+
* Returns `null` when grouping is disabled, matching the per-plugin convention.
|
|
904
|
+
*
|
|
905
|
+
* @param group - The user-supplied group option, or `undefined` to disable grouping.
|
|
906
|
+
* @param options.suffix - Appended to non-`path` group names, e.g. `'Controller'` or `'Requests'`.
|
|
907
|
+
* @param options.honorName - When `true`, a user-provided `group.name` overrides the default namer.
|
|
908
|
+
*
|
|
909
|
+
* @example
|
|
910
|
+
* ```ts
|
|
911
|
+
* createGroupConfig(group, { suffix: 'Controller' }) // plugin-ts, plugin-zod
|
|
912
|
+
* createGroupConfig(group, { suffix: 'Controller', honorName: true }) // plugin-faker, plugin-client, …
|
|
913
|
+
* createGroupConfig(group, { suffix: 'Requests', honorName: true }) // plugin-cypress, plugin-mcp
|
|
914
|
+
* ```
|
|
915
|
+
*/
|
|
916
|
+
function createGroupConfig(group, options) {
|
|
917
|
+
if (!group) return null;
|
|
918
|
+
const defaultName = (ctx) => {
|
|
919
|
+
if (group.type === "path") return `${ctx.group.split("/")[1]}`;
|
|
920
|
+
return `${camelCase(ctx.group)}${options.suffix}`;
|
|
921
|
+
};
|
|
922
|
+
return {
|
|
923
|
+
...group,
|
|
924
|
+
name: options.honorName && group.name ? group.name : defaultName
|
|
925
|
+
};
|
|
926
|
+
}
|
|
927
|
+
//#endregion
|
|
847
928
|
//#region src/utils.ts
|
|
848
929
|
/**
|
|
849
930
|
* Collects JSDoc annotation strings for a schema node.
|
|
@@ -965,7 +1046,7 @@ function buildResponses(node, { resolver }) {
|
|
|
965
1046
|
});
|
|
966
1047
|
}
|
|
967
1048
|
function buildResponseUnion(node, { resolver }) {
|
|
968
|
-
const responsesWithSchema = node.responses.filter((res) => res.content?.
|
|
1049
|
+
const responsesWithSchema = node.responses.filter((res) => res.content?.some((entry) => entry.schema));
|
|
969
1050
|
if (responsesWithSchema.length === 0) return null;
|
|
970
1051
|
return _kubb_core.ast.createSchema({
|
|
971
1052
|
type: "union",
|
|
@@ -1138,19 +1219,6 @@ const printerTs = _kubb_core.ast.definePrinter((options) => {
|
|
|
1138
1219
|
});
|
|
1139
1220
|
//#endregion
|
|
1140
1221
|
//#region src/generators/typeGenerator.tsx
|
|
1141
|
-
function getContentTypeSuffix(contentType) {
|
|
1142
|
-
const baseType = contentType.split(";")[0].trim();
|
|
1143
|
-
if (baseType === "application/json") return "Json";
|
|
1144
|
-
if (baseType === "multipart/form-data") return "FormData";
|
|
1145
|
-
if (baseType === "application/x-www-form-urlencoded") return "FormUrlEncoded";
|
|
1146
|
-
const parts = (baseType.split("/").pop() ?? baseType).split(/[^a-zA-Z0-9]+/).filter(Boolean);
|
|
1147
|
-
if (parts.length === 0) return "Unknown";
|
|
1148
|
-
return parts.map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join("");
|
|
1149
|
-
}
|
|
1150
|
-
function getPerContentTypeName(dataName, suffix) {
|
|
1151
|
-
if (dataName.endsWith("Data")) return suffix.endsWith("Data") ? dataName.slice(0, -4) + suffix : `${dataName.slice(0, -4)}${suffix}Data`;
|
|
1152
|
-
return dataName + suffix;
|
|
1153
|
-
}
|
|
1154
1222
|
/**
|
|
1155
1223
|
* Built-in generator for `@kubb/plugin-ts`. Emits one TypeScript file per
|
|
1156
1224
|
* schema in the spec plus per-operation request, response, and parameter
|
|
@@ -1310,6 +1378,28 @@ const typeGenerator = (0, _kubb_core.defineGenerator)({
|
|
|
1310
1378
|
printer: schemaPrinter
|
|
1311
1379
|
})] });
|
|
1312
1380
|
}
|
|
1381
|
+
/**
|
|
1382
|
+
* Emits an individual type per content type plus a union alias under `baseName`.
|
|
1383
|
+
* Shared by the request body and multi-content-type responses.
|
|
1384
|
+
*/
|
|
1385
|
+
function buildContentTypeVariants(entries, baseName, decorate) {
|
|
1386
|
+
const variants = resolveContentTypeVariants(entries, baseName);
|
|
1387
|
+
const unionSchema = _kubb_core.ast.createSchema({
|
|
1388
|
+
type: "union",
|
|
1389
|
+
members: variants.map((variant) => _kubb_core.ast.createSchema({
|
|
1390
|
+
type: "ref",
|
|
1391
|
+
name: variant.name
|
|
1392
|
+
}))
|
|
1393
|
+
});
|
|
1394
|
+
return /* @__PURE__ */ (0, _kubb_renderer_jsx_jsx_runtime.jsxs)(_kubb_renderer_jsx_jsx_runtime.Fragment, { children: [variants.map((variant) => renderSchemaType({
|
|
1395
|
+
schema: decorate ? decorate(variant.schema) : variant.schema,
|
|
1396
|
+
name: variant.name,
|
|
1397
|
+
keysToOmit: variant.keysToOmit
|
|
1398
|
+
})), renderSchemaType({
|
|
1399
|
+
schema: unionSchema,
|
|
1400
|
+
name: baseName
|
|
1401
|
+
})] });
|
|
1402
|
+
}
|
|
1313
1403
|
const paramTypes = params.map((param) => renderSchemaType({
|
|
1314
1404
|
schema: param.schema,
|
|
1315
1405
|
name: resolver.resolveParamName(node, param)
|
|
@@ -1329,44 +1419,22 @@ const typeGenerator = (0, _kubb_core.defineGenerator)({
|
|
|
1329
1419
|
keysToOmit: entry.keysToOmit
|
|
1330
1420
|
});
|
|
1331
1421
|
}
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
let individualName = getPerContentTypeName(dataName, baseSuffix);
|
|
1337
|
-
let counter = 2;
|
|
1338
|
-
while (usedNames.has(individualName)) individualName = getPerContentTypeName(dataName, `${baseSuffix}${counter++}`);
|
|
1339
|
-
usedNames.add(individualName);
|
|
1340
|
-
return {
|
|
1341
|
-
name: individualName,
|
|
1342
|
-
rendered: renderSchemaType({
|
|
1343
|
-
schema: {
|
|
1344
|
-
...entry.schema,
|
|
1345
|
-
description: node.requestBody.description ?? entry.schema.description
|
|
1346
|
-
},
|
|
1347
|
-
name: individualName,
|
|
1348
|
-
keysToOmit: entry.keysToOmit
|
|
1349
|
-
})
|
|
1350
|
-
};
|
|
1351
|
-
});
|
|
1352
|
-
const unionType = renderSchemaType({
|
|
1353
|
-
schema: _kubb_core.ast.createSchema({
|
|
1354
|
-
type: "union",
|
|
1355
|
-
members: individualItems.map((item) => _kubb_core.ast.createSchema({
|
|
1356
|
-
type: "ref",
|
|
1357
|
-
name: item.name
|
|
1358
|
-
}))
|
|
1359
|
-
}),
|
|
1360
|
-
name: dataName
|
|
1361
|
-
});
|
|
1362
|
-
return /* @__PURE__ */ (0, _kubb_renderer_jsx_jsx_runtime.jsxs)(_kubb_renderer_jsx_jsx_runtime.Fragment, { children: [individualItems.map((item) => item.rendered), unionType] });
|
|
1422
|
+
return buildContentTypeVariants(requestBodyContent, resolver.resolveDataName(node), (schema) => ({
|
|
1423
|
+
...schema,
|
|
1424
|
+
description: node.requestBody.description ?? schema.description
|
|
1425
|
+
}));
|
|
1363
1426
|
}
|
|
1364
1427
|
const requestType = buildRequestType();
|
|
1365
|
-
const responseTypes = node.responses.map((res) =>
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1428
|
+
const responseTypes = node.responses.map((res) => {
|
|
1429
|
+
const variants = (res.content ?? []).filter((entry) => entry.schema);
|
|
1430
|
+
if (variants.length > 1) return buildContentTypeVariants(variants, resolver.resolveResponseStatusName(node, res.statusCode));
|
|
1431
|
+
const primary = variants[0] ?? res.content?.[0];
|
|
1432
|
+
return renderSchemaType({
|
|
1433
|
+
schema: primary?.schema ?? null,
|
|
1434
|
+
name: resolver.resolveResponseStatusName(node, res.statusCode),
|
|
1435
|
+
keysToOmit: primary?.keysToOmit
|
|
1436
|
+
});
|
|
1437
|
+
});
|
|
1370
1438
|
const dataType = renderSchemaType({
|
|
1371
1439
|
schema: buildData({
|
|
1372
1440
|
...node,
|
|
@@ -1379,13 +1447,14 @@ const typeGenerator = (0, _kubb_core.defineGenerator)({
|
|
|
1379
1447
|
name: resolver.resolveResponsesName(node)
|
|
1380
1448
|
});
|
|
1381
1449
|
function buildResponseType() {
|
|
1382
|
-
|
|
1450
|
+
const hasSchema = (res) => (res.content ?? []).some((entry) => entry.schema);
|
|
1451
|
+
if (!node.responses.some(hasSchema)) return null;
|
|
1383
1452
|
const responseName = resolver.resolveResponseName(node);
|
|
1384
|
-
const responsesWithSchema = node.responses.filter(
|
|
1385
|
-
if (new Set(responsesWithSchema.flatMap((res) => res.content
|
|
1453
|
+
const responsesWithSchema = node.responses.filter(hasSchema);
|
|
1454
|
+
if (new Set(responsesWithSchema.flatMap((res) => (res.content ?? []).flatMap((entry) => entry.schema ? adapter.getImports(entry.schema, (schemaName) => ({
|
|
1386
1455
|
name: resolveImportName(schemaName),
|
|
1387
1456
|
path: ""
|
|
1388
|
-
})).flatMap((imp) => Array.isArray(imp.name) ? imp.name : [imp.name]) : [])).has(responseName)) return null;
|
|
1457
|
+
})).flatMap((imp) => Array.isArray(imp.name) ? imp.name : [imp.name]) : []))).has(responseName)) return null;
|
|
1389
1458
|
return renderSchemaType({
|
|
1390
1459
|
schema: {
|
|
1391
1460
|
...buildResponseUnion(node, { resolver }),
|
|
@@ -1529,13 +1598,7 @@ const pluginTs = (0, _kubb_core.definePlugin)((options) => {
|
|
|
1529
1598
|
path: "types",
|
|
1530
1599
|
barrelType: "named"
|
|
1531
1600
|
}, group, exclude = [], include, override = [], enumType = "asConst", enumTypeSuffix = "Key", enumKeyCasing = "none", optionalType = "questionToken", arrayType = "array", syntaxType = "type", paramsCasing, printer, resolver: userResolver, transformer: userTransformer, generators: userGenerators = [] } = options;
|
|
1532
|
-
const groupConfig = group
|
|
1533
|
-
...group,
|
|
1534
|
-
name: (ctx) => {
|
|
1535
|
-
if (group.type === "path") return `${ctx.group.split("/")[1]}`;
|
|
1536
|
-
return `${camelCase(ctx.group)}Controller`;
|
|
1537
|
-
}
|
|
1538
|
-
} : null;
|
|
1601
|
+
const groupConfig = createGroupConfig(group, { suffix: "Controller" });
|
|
1539
1602
|
return {
|
|
1540
1603
|
name: pluginTsName,
|
|
1541
1604
|
options,
|