@kubb/plugin-ts 5.0.0-beta.42 → 5.0.0-beta.56

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.js CHANGED
@@ -1,6 +1,7 @@
1
1
  import { t as __name } from "./chunk-C0LytTxp.js";
2
+ import { extractRefName, jsStringEscape, stringify, trimQuotes } from "@kubb/ast/utils";
2
3
  import { parserTs } from "@kubb/parser-ts";
3
- import { File, jsxRendererSync } from "@kubb/renderer-jsx";
4
+ import { File, jsxRenderer } from "@kubb/renderer-jsx";
4
5
  import { ast, defineGenerator, definePlugin, defineResolver } from "@kubb/core";
5
6
  import ts from "typescript";
6
7
  import { Fragment, jsx, jsxs } from "@kubb/renderer-jsx/jsx-runtime";
@@ -15,58 +16,41 @@ import { Fragment, jsx, jsxs } from "@kubb/renderer-jsx/jsx-runtime";
15
16
  function toCamelOrPascal(text, pascal) {
16
17
  return text.trim().replace(/([a-z\d])([A-Z])/g, "$1 $2").replace(/([A-Z]+)([A-Z][a-z])/g, "$1 $2").replace(/(\d)([a-z])/g, "$1 $2").split(/[\s\-_./\\:]+/).filter(Boolean).map((word, i) => {
17
18
  if (word.length > 1 && word === word.toUpperCase()) return word;
18
- if (i === 0 && !pascal) return word.charAt(0).toLowerCase() + word.slice(1);
19
- return word.charAt(0).toUpperCase() + word.slice(1);
19
+ return (i === 0 && !pascal ? word.charAt(0).toLowerCase() : word.charAt(0).toUpperCase()) + word.slice(1);
20
20
  }).join("").replace(/[^a-zA-Z0-9]/g, "");
21
21
  }
22
22
  /**
23
- * Splits `text` on `.` and applies `transformPart` to each segment.
24
- * The last segment receives `isLast = true`, all earlier segments receive `false`.
25
- * Segments are joined with `/` to form a file path.
26
- *
27
- * Only splits on dots followed by a letter so that version numbers
28
- * embedded in operationIds (e.g. `v2025.0`) are kept intact.
29
- */
30
- function applyToFileParts(text, transformPart) {
31
- const parts = text.split(/\.(?=[a-zA-Z])/);
32
- return parts.map((part, i) => transformPart(part, i === parts.length - 1)).join("/");
33
- }
34
- /**
35
23
  * Converts `text` to camelCase.
36
- * When `isFile` is `true`, dot-separated segments are each cased independently and joined with `/`.
37
24
  *
38
- * @example
39
- * camelCase('hello-world') // 'helloWorld'
40
- * camelCase('pet.petId', { isFile: true }) // 'pet/petId'
25
+ * @example Word boundaries
26
+ * `camelCase('hello-world') // 'helloWorld'`
27
+ *
28
+ * @example With a prefix
29
+ * `camelCase('tag', { prefix: 'create' }) // 'createTag'`
41
30
  */
42
- function camelCase(text, { isFile, prefix = "", suffix = "" } = {}) {
43
- if (isFile) return applyToFileParts(text, (part, isLast) => camelCase(part, isLast ? {
44
- prefix,
45
- suffix
46
- } : {}));
31
+ function camelCase(text, { prefix = "", suffix = "" } = {}) {
47
32
  return toCamelOrPascal(`${prefix} ${text} ${suffix}`, false);
48
33
  }
49
34
  /**
50
35
  * Converts `text` to PascalCase.
51
- * When `isFile` is `true`, the last dot-separated segment is PascalCased and earlier segments are camelCased.
52
36
  *
53
- * @example
54
- * pascalCase('hello-world') // 'HelloWorld'
55
- * pascalCase('pet.petId', { isFile: true }) // 'pet/PetId'
37
+ * @example Word boundaries
38
+ * `pascalCase('hello-world') // 'HelloWorld'`
39
+ *
40
+ * @example With a suffix
41
+ * `pascalCase('tag', { suffix: 'schema' }) // 'TagSchema'`
56
42
  */
57
- function pascalCase(text, { isFile, prefix = "", suffix = "" } = {}) {
58
- if (isFile) return applyToFileParts(text, (part, isLast) => isLast ? pascalCase(part, {
59
- prefix,
60
- suffix
61
- }) : camelCase(part));
43
+ function pascalCase(text, { prefix = "", suffix = "" } = {}) {
62
44
  return toCamelOrPascal(`${prefix} ${text} ${suffix}`, true);
63
45
  }
64
46
  /**
65
47
  * Converts `text` to snake_case.
66
48
  *
67
- * @example
68
- * snakeCase('helloWorld') // 'hello_world'
69
- * snakeCase('Hello-World') // 'hello_world'
49
+ * @example From camelCase
50
+ * `snakeCase('helloWorld') // 'hello_world'`
51
+ *
52
+ * @example From mixed separators
53
+ * `snakeCase('Hello-World') // 'hello_world'`
70
54
  */
71
55
  function snakeCase(text, { prefix = "", suffix = "" } = {}) {
72
56
  return `${prefix} ${text} ${suffix}`.trim().replace(/([a-z])([A-Z])/g, "$1_$2").replace(/[\s\-.]+/g, "_").replace(/[^a-zA-Z0-9_]/g, "").toLowerCase().split("_").filter(Boolean).join("_");
@@ -74,8 +58,8 @@ function snakeCase(text, { prefix = "", suffix = "" } = {}) {
74
58
  /**
75
59
  * Converts `text` to SCREAMING_SNAKE_CASE.
76
60
  *
77
- * @example
78
- * screamingSnakeCase('helloWorld') // 'HELLO_WORLD'
61
+ * @example From camelCase
62
+ * `screamingSnakeCase('helloWorld') // 'HELLO_WORLD'`
79
63
  */
80
64
  function screamingSnakeCase(text, { prefix = "", suffix = "" } = {}) {
81
65
  return snakeCase(text, {
@@ -84,60 +68,29 @@ function screamingSnakeCase(text, { prefix = "", suffix = "" } = {}) {
84
68
  }).toUpperCase();
85
69
  }
86
70
  //#endregion
87
- //#region ../../internals/utils/src/string.ts
71
+ //#region ../../internals/utils/src/fs.ts
88
72
  /**
89
- * Strips a single matching pair of `"..."`, `'...'`, or `` `...` `` from both ends of `text`.
90
- * Returns the string unchanged when no balanced quote pair is found.
73
+ * Builds a nested file path from a dotted name. Splits on dots that precede a letter
74
+ * (so version numbers embedded in operationIds like `v2025.0` stay intact), camelCases
75
+ * every earlier segment, applies `caseLast` to the final segment, and joins with `/`.
91
76
  *
92
- * @example
93
- * trimQuotes('"hello"') // 'hello'
94
- * trimQuotes('hello') // 'hello'
95
- */
96
- function trimQuotes(text) {
97
- if (text.length >= 2) {
98
- const first = text[0];
99
- const last = text[text.length - 1];
100
- if (first === "\"" && last === "\"" || first === "'" && last === "'" || first === "`" && last === "`") return text.slice(1, -1);
101
- }
102
- return text;
103
- }
104
- /**
105
- * Escapes characters that are not allowed inside JS string literals.
106
- * Handles quotes, backslashes, and Unicode line terminators (U+2028 / U+2029).
77
+ * Empty segments are dropped before joining. They arise when the name starts with a dot
78
+ * followed by a letter (e.g. `..Schema` splits into `['..', 'Schema']` and `'..'` cases to
79
+ * an empty string). Without this a leading `/` would form, which `path.resolve` reads as an
80
+ * absolute path, letting generated files escape the configured output directory.
107
81
  *
108
- * @see http://www.ecma-international.org/ecma-262/5.1/#sec-7.8.4
82
+ * @example Nested path from a dotted name
83
+ * `toFilePath('pet.petId') // 'pet/petId'`
109
84
  *
110
- * @example
111
- * ```ts
112
- * jsStringEscape('say "hi"\nbye') // 'say \\"hi\\"\\nbye'
113
- * ```
114
- */
115
- function jsStringEscape(input) {
116
- return `${input}`.replace(/["'\\\n\r\u2028\u2029]/g, (character) => {
117
- switch (character) {
118
- case "\"":
119
- case "'":
120
- case "\\": return `\\${character}`;
121
- case "\n": return "\\n";
122
- case "\r": return "\\r";
123
- case "\u2028": return "\\u2028";
124
- case "\u2029": return "\\u2029";
125
- default: return "";
126
- }
127
- });
128
- }
129
- //#endregion
130
- //#region ../../internals/utils/src/object.ts
131
- /**
132
- * Serializes a primitive value to a JSON string literal, stripping any surrounding quote characters first.
85
+ * @example PascalCase the final segment
86
+ * `toFilePath('pet.Pet', pascalCase) // 'pet/Pet'`
133
87
  *
134
- * @example
135
- * stringify('hello') // '"hello"'
136
- * stringify('"hello"') // '"hello"'
88
+ * @example Suffix applied to the final segment only
89
+ * `toFilePath('tag.tag', (part) => camelCase(part, { suffix: 'schema' })) // 'tag/tagSchema'`
137
90
  */
138
- function stringify(value) {
139
- if (value === void 0 || value === null) return "\"\"";
140
- return JSON.stringify(trimQuotes(value.toString()));
91
+ function toFilePath(name, caseLast = camelCase) {
92
+ const parts = name.split(/\.(?=[a-zA-Z])/);
93
+ return parts.map((part, i) => i === parts.length - 1 ? caseLast(part) : camelCase(part)).filter(Boolean).join("/");
141
94
  }
142
95
  //#endregion
143
96
  //#region ../../internals/utils/src/reserved.ts
@@ -273,26 +226,24 @@ const OPTIONAL_ADDS_UNDEFINED = new Set(["undefined", "questionTokenAndUndefined
273
226
  */
274
227
  const OPTIONAL_ADDS_QUESTION_TOKEN = new Set(["questionToken", "questionTokenAndUndefined"]);
275
228
  /**
276
- * `enumType` values that append a `Key` suffix to the generated enum type alias.
229
+ * `enum.type` values that append a `typeSuffix` to the generated enum type alias.
277
230
  */
278
- const ENUM_TYPES_WITH_KEY_SUFFIX = new Set(["asConst", "asPascalConst"]);
231
+ const ENUM_TYPES_WITH_KEY_SUFFIX = new Set(["asConst"]);
279
232
  /**
280
- * `enumType` values that require a runtime value declaration (object, enum, or literal).
233
+ * `enum.type` values that require a runtime value declaration (object, enum, or literal).
281
234
  */
282
235
  const ENUM_TYPES_WITH_RUNTIME_VALUE = new Set([
283
236
  "enum",
284
237
  "asConst",
285
- "asPascalConst",
286
238
  "constEnum",
287
239
  "literal",
288
240
  void 0
289
241
  ]);
290
242
  /**
291
- * `enumType` values whose type declaration is type-only (no runtime value emitted for the type alias).
243
+ * `enum.type` values whose type declaration is type-only (no runtime value emitted for the type alias).
292
244
  */
293
245
  const ENUM_TYPES_WITH_TYPE_ONLY = new Set([
294
246
  "asConst",
295
- "asPascalConst",
296
247
  "literal",
297
248
  void 0
298
249
  ]);
@@ -612,12 +563,10 @@ const createStringLiteral = factory.createStringLiteral;
612
563
  * Creates an array type node (e.g., `T[]`).
613
564
  */
614
565
  const createArrayTypeNode = factory.createArrayTypeNode;
615
- factory.createParenthesizedType;
616
566
  /**
617
567
  * Creates a literal type node (e.g., `'hello'`, `42`, `true`).
618
568
  */
619
569
  const createLiteralTypeNode = factory.createLiteralTypeNode;
620
- factory.createNull;
621
570
  /**
622
571
  * Creates an identifier node.
623
572
  */
@@ -642,8 +591,6 @@ const createTrue = factory.createTrue;
642
591
  * Creates a boolean false literal type node.
643
592
  */
644
593
  const createFalse = factory.createFalse;
645
- factory.createIndexedAccessTypeNode;
646
- factory.createTypeOperatorNode;
647
594
  /**
648
595
  * Creates a prefix unary expression (e.g., negative numbers, logical not).
649
596
  */
@@ -724,41 +671,44 @@ function buildIndexSignatures(node, propertyCount, print) {
724
671
  * Resolves the runtime identifier name and the TypeScript type name for an enum schema node.
725
672
  *
726
673
  * The raw `node.name` may be a YAML key such as `"enumNames.Type"` which is not a
727
- * valid TypeScript identifier. The resolver normalizes it; for inline enum
728
- * properties the adapter already emits a PascalCase+suffix name so resolution is typically a no-op.
674
+ * valid TypeScript identifier. The resolver normalizes it. For inline enum properties the adapter
675
+ * already emits a PascalCase+suffix name, so resolution is typically a no-op.
676
+ *
677
+ * When `constCasing` is `'pascalCase'` and `typeSuffix` is empty, the const and the type
678
+ * resolve to the same name, which TypeScript merges into a single value+type declaration.
729
679
  */
730
- function getEnumNames({ node, enumType, enumTypeSuffix, resolver }) {
680
+ function getEnumNames({ node, enum: enumOptions, resolver }) {
731
681
  const resolved = resolver.default(node.name, "type");
732
682
  return {
733
- enumName: enumType === "asPascalConst" ? resolved : camelCase(node.name),
734
- typeName: ENUM_TYPES_WITH_KEY_SUFFIX.has(enumType) ? resolver.resolveEnumKeyName(node, enumTypeSuffix) : resolved
683
+ enumName: enumOptions.constCasing === "pascalCase" ? resolved : camelCase(node.name),
684
+ typeName: ENUM_TYPES_WITH_KEY_SUFFIX.has(enumOptions.type) ? resolver.resolveEnumKeyName(node, enumOptions.typeSuffix) : resolved
735
685
  };
736
686
  }
737
687
  /**
738
688
  * Renders the enum declaration(s) for a single named `EnumSchemaNode`.
739
689
  *
740
- * Depending on `enumType` this may emit:
741
- * - A runtime object (`asConst` / `asPascalConst`) plus a `typeof` type alias
690
+ * Depending on `enum.type` this may emit:
691
+ * - A runtime object (`asConst`) plus a `typeof` type alias
742
692
  * - A `const enum` or plain `enum` declaration (`constEnum` / `enum`)
743
693
  * - A union literal type alias (`literal`)
744
694
  *
745
695
  * The emitted `File.Source` nodes carry the resolved names so that the barrel
746
696
  * index picks up the correct export identifiers.
747
697
  */
748
- function Enum({ node, enumType, enumTypeSuffix, enumKeyCasing, resolver }) {
698
+ function Enum({ node, enum: enumOptions, resolver }) {
749
699
  const { enumName, typeName } = getEnumNames({
750
700
  node,
751
- enumType,
752
- enumTypeSuffix,
701
+ enum: enumOptions,
753
702
  resolver
754
703
  });
755
704
  const [nameNode, typeNode] = createEnumDeclaration({
756
705
  name: enumName,
757
706
  typeName,
758
707
  enums: node.namedEnumValues?.map((v) => [trimQuotes(v.name.toString()), v.value]) ?? node.enumValues?.filter((v) => v !== null && v !== void 0).map((v) => [trimQuotes(v.toString()), v]) ?? [],
759
- type: enumType,
760
- enumKeyCasing
708
+ type: enumOptions.type,
709
+ enumKeyCasing: enumOptions.keyCasing
761
710
  });
711
+ const namesMerge = !!nameNode && enumName === typeName;
762
712
  return /* @__PURE__ */ jsxs(Fragment, { children: [nameNode && /* @__PURE__ */ jsx(File.Source, {
763
713
  name: enumName,
764
714
  isExportable: true,
@@ -767,15 +717,15 @@ function Enum({ node, enumType, enumTypeSuffix, enumKeyCasing, resolver }) {
767
717
  children: parserTs.print(nameNode)
768
718
  }), /* @__PURE__ */ jsx(File.Source, {
769
719
  name: typeName,
770
- isIndexable: true,
771
- isExportable: ENUM_TYPES_WITH_RUNTIME_VALUE.has(enumType),
772
- isTypeOnly: ENUM_TYPES_WITH_TYPE_ONLY.has(enumType),
720
+ isIndexable: !namesMerge,
721
+ isExportable: !namesMerge && ENUM_TYPES_WITH_RUNTIME_VALUE.has(enumOptions.type),
722
+ isTypeOnly: ENUM_TYPES_WITH_TYPE_ONLY.has(enumOptions.type),
773
723
  children: parserTs.print(typeNode)
774
724
  })] });
775
725
  }
776
726
  //#endregion
777
727
  //#region src/components/Type.tsx
778
- function Type({ name, node, printer, enumType, enumTypeSuffix, enumKeyCasing, resolver }) {
728
+ function Type({ name, node, printer, enum: enumOptions, resolver }) {
779
729
  const enumSchemaNodes = ast.collect(node, { schema(n) {
780
730
  const enumNode = ast.narrowSchema(n, ast.schemaTypes.enum);
781
731
  if (enumNode?.name) return enumNode;
@@ -787,19 +737,16 @@ function Type({ name, node, printer, enumType, enumTypeSuffix, enumKeyCasing, re
787
737
  node,
788
738
  ...getEnumNames({
789
739
  node,
790
- enumType,
791
- enumTypeSuffix,
740
+ enum: enumOptions,
792
741
  resolver
793
742
  })
794
743
  };
795
744
  });
796
- const shouldExportEnums = enumType !== "inlineLiteral";
797
- const shouldExportType = enumType === "inlineLiteral" || enums.every((item) => item.typeName !== name);
745
+ const shouldExportEnums = enumOptions.type !== "inlineLiteral";
746
+ const shouldExportType = enumOptions.type === "inlineLiteral" || enums.every((item) => item.typeName !== name);
798
747
  return /* @__PURE__ */ jsxs(Fragment, { children: [shouldExportEnums && enums.map(({ node }) => /* @__PURE__ */ jsx(Enum, {
799
748
  node,
800
- enumType,
801
- enumTypeSuffix,
802
- enumKeyCasing,
749
+ enum: enumOptions,
803
750
  resolver
804
751
  }, node.name)), shouldExportType && /* @__PURE__ */ jsx(File.Source, {
805
752
  name,
@@ -875,26 +822,24 @@ function getOperationParameters(node, options = {}) {
875
822
  * shared default naming so every plugin groups output consistently:
876
823
  *
877
824
  * - `path` groups use the second path segment (`/pet/findByStatus` → `pet`).
878
- * - other groups use `${camelCase(group)}${suffix}` (e.g. `petController`).
825
+ * - other groups use the camelCased group (`pet store``petStore`).
879
826
  *
880
827
  * A user-provided `group.name` always wins over the default namer, so callers stay in
881
828
  * control of their output folders. Returns `null` when grouping is disabled, matching the
882
829
  * per-plugin convention.
883
830
  *
884
831
  * @param group - The user-supplied group option, or `undefined` to disable grouping.
885
- * @param options.suffix - Appended to non-`path` group names, e.g. `'Controller'` or `'Requests'`.
886
832
  *
887
833
  * @example
888
834
  * ```ts
889
- * createGroupConfig(group, { suffix: 'Controller' }) // plugin-ts, plugin-client, …
890
- * createGroupConfig(group, { suffix: 'Requests' }) // plugin-cypress, plugin-mcp
835
+ * createGroupConfig(group) // shared across every plugin
891
836
  * ```
892
837
  */
893
- function createGroupConfig(group, options) {
838
+ function createGroupConfig(group) {
894
839
  if (!group) return null;
895
840
  const defaultName = (ctx) => {
896
841
  if (group.type === "path") return `${ctx.group.split("/")[1]}`;
897
- return `${camelCase(ctx.group)}${options.suffix}`;
842
+ return camelCase(ctx.group);
898
843
  };
899
844
  return {
900
845
  ...group,
@@ -1051,13 +996,13 @@ function isNonNullable(value) {
1051
996
  *
1052
997
  * @example Raw type node (no `typeName`)
1053
998
  * ```ts
1054
- * const printer = printerTs({ optionalType: 'questionToken', arrayType: 'array', enumType: 'inlineLiteral' })
999
+ * const printer = printerTs({ optionalType: 'questionToken', arrayType: 'array', enum: { type: 'inlineLiteral' } })
1055
1000
  * const typeNode = printer.print(schemaNode) // ts.TypeNode
1056
1001
  * ```
1057
1002
  *
1058
1003
  * @example Full declaration (with `typeName`)
1059
1004
  * ```ts
1060
- * const printer = printerTs({ optionalType: 'questionToken', arrayType: 'array', enumType: 'inlineLiteral', typeName: 'MyType' })
1005
+ * const printer = printerTs({ optionalType: 'questionToken', arrayType: 'array', enum: { type: 'inlineLiteral' }, typeName: 'MyType' })
1061
1006
  * const declaration = printer.print(schemaNode) // ts.TypeAliasDeclaration | ts.InterfaceDeclaration
1062
1007
  * ```
1063
1008
  */
@@ -1091,16 +1036,16 @@ const printerTs = ast.definePrinter((options) => {
1091
1036
  time: dateOrStringNode,
1092
1037
  ref(node) {
1093
1038
  if (!node.name) return null;
1094
- const refName = node.ref ? ast.extractRefName(node.ref) ?? node.name : node.name;
1095
- return createTypeReferenceNode(node.ref && ENUM_TYPES_WITH_KEY_SUFFIX.has(this.options.enumType) && this.options.enumTypeSuffix && this.options.enumSchemaNames?.has(refName) ? this.options.resolver.resolveEnumKeyName({ name: refName }, this.options.enumTypeSuffix) : node.ref ? this.options.resolver.default(refName, "type") : refName, void 0);
1039
+ const refName = node.ref ? extractRefName(node.ref) ?? node.name : node.name;
1040
+ return createTypeReferenceNode(node.ref && ENUM_TYPES_WITH_KEY_SUFFIX.has(this.options.enum.type) && this.options.enum.typeSuffix && this.options.enumSchemaNames?.has(refName) ? this.options.resolver.resolveEnumKeyName({ name: refName }, this.options.enum.typeSuffix) : node.ref ? this.options.resolver.default(refName, "type") : refName, void 0);
1096
1041
  },
1097
1042
  enum(node) {
1098
1043
  const values = node.namedEnumValues?.map((v) => v.value) ?? node.enumValues ?? [];
1099
- if (this.options.enumType === "inlineLiteral" || !node.name) return createUnionDeclaration({
1044
+ if (this.options.enum.type === "inlineLiteral" || !node.name) return createUnionDeclaration({
1100
1045
  withParentheses: true,
1101
1046
  nodes: values.filter((v) => v !== null && v !== void 0).map((value) => constToTypeNode(value, typeof value)).filter(isNonNullable)
1102
1047
  }) ?? void 0;
1103
- return createTypeReferenceNode(ENUM_TYPES_WITH_KEY_SUFFIX.has(this.options.enumType) && this.options.enumTypeSuffix ? this.options.resolver.resolveEnumKeyName(node, this.options.enumTypeSuffix) : this.options.resolver.default(node.name, "type"), void 0);
1048
+ return createTypeReferenceNode(ENUM_TYPES_WITH_KEY_SUFFIX.has(this.options.enum.type) && this.options.enum.typeSuffix ? this.options.resolver.resolveEnumKeyName(node, this.options.enum.typeSuffix) : this.options.resolver.default(node.name, "type"), void 0);
1104
1049
  },
1105
1050
  union(node) {
1106
1051
  const members = node.members ?? [];
@@ -1204,15 +1149,14 @@ const printerTs = ast.definePrinter((options) => {
1204
1149
  */
1205
1150
  const typeGenerator = defineGenerator({
1206
1151
  name: "typescript",
1207
- renderer: jsxRendererSync,
1152
+ renderer: jsxRenderer,
1208
1153
  schema(node, ctx) {
1209
- const { enumType, enumTypeSuffix, enumKeyCasing, syntaxType, optionalType, arrayType, output, group, printer } = ctx.options;
1154
+ const { enum: enumOptions, syntaxType, optionalType, arrayType, output, group, printer } = ctx.options;
1210
1155
  const { adapter, config, resolver, root } = ctx;
1211
1156
  if (!node.name) return;
1212
- const mode = ctx.getMode(output);
1213
1157
  const enumSchemaNames = new Set(ctx.meta.enumNames);
1214
1158
  function resolveImportName(schemaName) {
1215
- if (ENUM_TYPES_WITH_KEY_SUFFIX.has(enumType) && enumTypeSuffix && enumSchemaNames.has(schemaName)) return resolver.resolveEnumKeyName({ name: schemaName }, enumTypeSuffix);
1159
+ if (ENUM_TYPES_WITH_KEY_SUFFIX.has(enumOptions.type) && enumOptions.typeSuffix && enumSchemaNames.has(schemaName)) return resolver.resolveEnumKeyName({ name: schemaName }, enumOptions.typeSuffix);
1216
1160
  return resolver.resolveTypeName(schemaName);
1217
1161
  }
1218
1162
  const imports = adapter.getImports(node, (schemaName) => ({
@@ -1228,7 +1172,7 @@ const typeGenerator = defineGenerator({
1228
1172
  }));
1229
1173
  const isEnumSchema = !!ast.narrowSchema(node, ast.schemaTypes.enum);
1230
1174
  const meta = {
1231
- name: ENUM_TYPES_WITH_KEY_SUFFIX.has(enumType) && isEnumSchema ? resolver.resolveEnumKeyName(node, enumTypeSuffix) : resolver.resolveTypeName(node.name),
1175
+ name: ENUM_TYPES_WITH_KEY_SUFFIX.has(enumOptions.type) && isEnumSchema ? resolver.resolveEnumKeyName(node, enumOptions.typeSuffix) : resolver.resolveTypeName(node.name),
1232
1176
  file: resolver.resolveFile({
1233
1177
  name: node.name,
1234
1178
  extname: ".ts"
@@ -1241,8 +1185,7 @@ const typeGenerator = defineGenerator({
1241
1185
  const schemaPrinter = printerTs({
1242
1186
  optionalType,
1243
1187
  arrayType,
1244
- enumType,
1245
- enumTypeSuffix,
1188
+ enum: enumOptions,
1246
1189
  name: meta.name,
1247
1190
  syntaxType,
1248
1191
  description: node.description,
@@ -1270,7 +1213,7 @@ const typeGenerator = defineGenerator({
1270
1213
  baseName: meta.file.baseName
1271
1214
  }
1272
1215
  }),
1273
- children: [mode === "split" && imports.map((imp) => /* @__PURE__ */ jsx(File.Import, {
1216
+ children: [imports.map((imp) => /* @__PURE__ */ jsx(File.Import, {
1274
1217
  root: meta.file.path,
1275
1218
  path: imp.path,
1276
1219
  name: imp.name,
@@ -1282,18 +1225,15 @@ const typeGenerator = defineGenerator({
1282
1225
  ].join("-"))), /* @__PURE__ */ jsx(Type, {
1283
1226
  name: meta.name,
1284
1227
  node,
1285
- enumType,
1286
- enumTypeSuffix,
1287
- enumKeyCasing,
1228
+ enum: enumOptions,
1288
1229
  resolver,
1289
1230
  printer: schemaPrinter
1290
1231
  })]
1291
1232
  });
1292
1233
  },
1293
1234
  operation(node, ctx) {
1294
- const { enumType, enumTypeSuffix, enumKeyCasing, optionalType, arrayType, syntaxType, paramsCasing, group, output, printer } = ctx.options;
1235
+ const { enum: enumOptions, optionalType, arrayType, syntaxType, paramsCasing, group, output, printer } = ctx.options;
1295
1236
  const { adapter, config, resolver, root } = ctx;
1296
- const mode = ctx.getMode(output);
1297
1237
  const params = ast.caseParams(node.parameters, paramsCasing);
1298
1238
  const meta = { file: resolver.resolveFile({
1299
1239
  name: node.operationId,
@@ -1307,7 +1247,7 @@ const typeGenerator = defineGenerator({
1307
1247
  }) };
1308
1248
  const enumSchemaNames = new Set(ctx.meta.enumNames);
1309
1249
  function resolveImportName(schemaName) {
1310
- if (ENUM_TYPES_WITH_KEY_SUFFIX.has(enumType) && enumTypeSuffix && enumSchemaNames.has(schemaName)) return resolver.resolveEnumKeyName({ name: schemaName }, enumTypeSuffix);
1250
+ if (ENUM_TYPES_WITH_KEY_SUFFIX.has(enumOptions.type) && enumOptions.typeSuffix && enumSchemaNames.has(schemaName)) return resolver.resolveEnumKeyName({ name: schemaName }, enumOptions.typeSuffix);
1311
1251
  return resolver.resolveTypeName(schemaName);
1312
1252
  }
1313
1253
  function renderSchemaType({ schema, name, keysToOmit }) {
@@ -1326,8 +1266,7 @@ const typeGenerator = defineGenerator({
1326
1266
  const schemaPrinter = printerTs({
1327
1267
  optionalType,
1328
1268
  arrayType,
1329
- enumType,
1330
- enumTypeSuffix,
1269
+ enum: enumOptions,
1331
1270
  name,
1332
1271
  syntaxType,
1333
1272
  description: schema.description,
@@ -1336,7 +1275,7 @@ const typeGenerator = defineGenerator({
1336
1275
  enumSchemaNames,
1337
1276
  nodes: printer?.nodes
1338
1277
  });
1339
- return /* @__PURE__ */ jsxs(Fragment, { children: [mode === "split" && imports.map((imp) => /* @__PURE__ */ jsx(File.Import, {
1278
+ return /* @__PURE__ */ jsxs(Fragment, { children: [imports.map((imp) => /* @__PURE__ */ jsx(File.Import, {
1340
1279
  root: meta.file.path,
1341
1280
  path: imp.path,
1342
1281
  name: imp.name,
@@ -1348,9 +1287,7 @@ const typeGenerator = defineGenerator({
1348
1287
  ].join("-"))), /* @__PURE__ */ jsx(Type, {
1349
1288
  name,
1350
1289
  node: schema,
1351
- enumType,
1352
- enumTypeSuffix,
1353
- enumKeyCasing,
1290
+ enum: enumOptions,
1354
1291
  resolver,
1355
1292
  printer: schemaPrinter
1356
1293
  })] });
@@ -1481,7 +1418,7 @@ const typeGenerator = defineGenerator({
1481
1418
  * casing/file-layout rules.
1482
1419
  *
1483
1420
  * The `default` method is supplied by `defineResolver`. It uses PascalCase for
1484
- * type names and PascalCase-with-isFile for files.
1421
+ * type names and PascalCase file paths (dotted names become `/`-joined) for files.
1485
1422
  *
1486
1423
  * @example Resolve a type and file name
1487
1424
  * ```ts
@@ -1497,15 +1434,15 @@ const resolverTs = defineResolver(() => {
1497
1434
  name: "default",
1498
1435
  pluginName: "plugin-ts",
1499
1436
  default(name, type) {
1500
- const resolved = pascalCase(name, { isFile: type === "file" });
1501
- return type === "file" ? resolved : ensureValidVarName(resolved);
1437
+ if (type === "file") return toFilePath(name, pascalCase);
1438
+ return ensureValidVarName(pascalCase(name));
1502
1439
  },
1503
1440
  resolveTypeName(name) {
1504
1441
  return ensureValidVarName(pascalCase(name));
1505
1442
  },
1506
1443
  resolvePathName(name, type) {
1507
- const resolved = pascalCase(name, { isFile: type === "file" });
1508
- return type === "file" ? resolved : ensureValidVarName(resolved);
1444
+ if (type === "file") return toFilePath(name, pascalCase);
1445
+ return ensureValidVarName(pascalCase(name));
1509
1446
  },
1510
1447
  resolveParamName(node, param) {
1511
1448
  return this.resolveTypeName(`${node.operationId} ${param.in} ${param.name}`);
@@ -1563,7 +1500,7 @@ const pluginTsName = "plugin-ts";
1563
1500
  * plugins: [
1564
1501
  * pluginTs({
1565
1502
  * output: { path: './types' },
1566
- * enumType: 'asConst',
1503
+ * enum: { type: 'asConst' },
1567
1504
  * optionalType: 'questionTokenAndUndefined',
1568
1505
  * }),
1569
1506
  * ],
@@ -1573,9 +1510,15 @@ const pluginTsName = "plugin-ts";
1573
1510
  const pluginTs = definePlugin((options) => {
1574
1511
  const { output = {
1575
1512
  path: "types",
1576
- barrelType: "named"
1577
- }, 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;
1578
- const groupConfig = createGroupConfig(group, { suffix: "Controller" });
1513
+ barrel: { type: "named" }
1514
+ }, group, exclude = [], include, override = [], enum: enumOptions = {}, optionalType = "questionToken", arrayType = "array", syntaxType = "type", paramsCasing, printer, resolver: userResolver, transformer: userTransformer, generators: userGenerators = [] } = options;
1515
+ const groupConfig = createGroupConfig(group);
1516
+ const resolvedEnum = {
1517
+ type: enumOptions.type ?? "asConst",
1518
+ constCasing: enumOptions.constCasing ?? "camelCase",
1519
+ typeSuffix: enumOptions.typeSuffix ?? "Key",
1520
+ keyCasing: enumOptions.keyCasing ?? "none"
1521
+ };
1579
1522
  return {
1580
1523
  name: pluginTsName,
1581
1524
  options,
@@ -1588,9 +1531,7 @@ const pluginTs = definePlugin((options) => {
1588
1531
  optionalType,
1589
1532
  group: groupConfig,
1590
1533
  arrayType,
1591
- enumType,
1592
- enumTypeSuffix,
1593
- enumKeyCasing,
1534
+ enum: resolvedEnum,
1594
1535
  syntaxType,
1595
1536
  paramsCasing,
1596
1537
  printer