@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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kubb/plugin-ts",
3
- "version": "5.0.0-beta.42",
3
+ "version": "5.0.0-beta.56",
4
4
  "description": "Generate TypeScript types, interfaces, and enums from your OpenAPI specification. The foundational plugin that powers type safety across the entire Kubb ecosystem.",
5
5
  "keywords": [
6
6
  "code-generation",
@@ -24,7 +24,6 @@
24
24
  "files": [
25
25
  "src",
26
26
  "dist",
27
- "extension.yaml",
28
27
  "!/**/**.test.**",
29
28
  "!/**/__tests__/**",
30
29
  "!/**/__snapshots__/**"
@@ -46,9 +45,10 @@
46
45
  "registry": "https://registry.npmjs.org/"
47
46
  },
48
47
  "dependencies": {
49
- "@kubb/core": "5.0.0-beta.42",
50
- "@kubb/parser-ts": "5.0.0-beta.42",
51
- "@kubb/renderer-jsx": "5.0.0-beta.42",
48
+ "@kubb/ast": "5.0.0-beta.55",
49
+ "@kubb/core": "5.0.0-beta.55",
50
+ "@kubb/parser-ts": "5.0.0-beta.55",
51
+ "@kubb/renderer-jsx": "5.0.0-beta.55",
52
52
  "typescript": "^6.0.3"
53
53
  },
54
54
  "devDependencies": {
@@ -56,21 +56,14 @@
56
56
  "@internals/utils": "0.0.0"
57
57
  },
58
58
  "peerDependencies": {
59
- "@kubb/renderer-jsx": "5.0.0-beta.42"
59
+ "@kubb/renderer-jsx": "5.0.0-beta.55"
60
60
  },
61
- "size-limit": [
62
- {
63
- "path": "./dist/*.js",
64
- "limit": "510 KiB",
65
- "gzip": true
66
- }
67
- ],
68
61
  "engines": {
69
62
  "node": ">=22"
70
63
  },
71
64
  "scripts": {
72
- "build": "tsdown && size-limit",
73
- "clean": "npx rimraf ./dist",
65
+ "build": "tsdown",
66
+ "clean": "node -e \"require('node:fs').rmSync('./dist', {recursive:true,force:true})\"",
74
67
  "lint": "oxlint .",
75
68
  "lint:fix": "oxlint --fix .",
76
69
  "release": "pnpm publish --no-git-check",
@@ -1,4 +1,5 @@
1
- import { camelCase, trimQuotes } from '@internals/utils'
1
+ import { camelCase } from '@internals/utils'
2
+ import { trimQuotes } from '@kubb/ast/utils'
2
3
  import type { ast } from '@kubb/core'
3
4
  import { parserTs } from '@kubb/parser-ts'
4
5
  import { File } from '@kubb/renderer-jsx'
@@ -7,11 +8,11 @@ import { ENUM_TYPES_WITH_KEY_SUFFIX, ENUM_TYPES_WITH_RUNTIME_VALUE, ENUM_TYPES_W
7
8
  import * as factory from '../factory.ts'
8
9
  import type { PluginTs, ResolverTs } from '../types.ts'
9
10
 
11
+ type EnumOptions = PluginTs['resolvedOptions']['enum']
12
+
10
13
  type Props = {
11
14
  node: ast.EnumSchemaNode
12
- enumType: PluginTs['resolvedOptions']['enumType']
13
- enumTypeSuffix: PluginTs['resolvedOptions']['enumTypeSuffix']
14
- enumKeyCasing: PluginTs['resolvedOptions']['enumKeyCasing']
15
+ enum: EnumOptions
15
16
  resolver: ResolverTs
16
17
  key?: string | number | null
17
18
  }
@@ -20,26 +21,19 @@ type Props = {
20
21
  * Resolves the runtime identifier name and the TypeScript type name for an enum schema node.
21
22
  *
22
23
  * The raw `node.name` may be a YAML key such as `"enumNames.Type"` which is not a
23
- * valid TypeScript identifier. The resolver normalizes it; for inline enum
24
- * properties the adapter already emits a PascalCase+suffix name so resolution is typically a no-op.
24
+ * valid TypeScript identifier. The resolver normalizes it. For inline enum properties the adapter
25
+ * already emits a PascalCase+suffix name, so resolution is typically a no-op.
26
+ *
27
+ * When `constCasing` is `'pascalCase'` and `typeSuffix` is empty, the const and the type
28
+ * resolve to the same name, which TypeScript merges into a single value+type declaration.
25
29
  */
26
- export function getEnumNames({
27
- node,
28
- enumType,
29
- enumTypeSuffix,
30
- resolver,
31
- }: {
32
- node: ast.EnumSchemaNode
33
- enumType: PluginTs['resolvedOptions']['enumType']
34
- enumTypeSuffix: PluginTs['resolvedOptions']['enumTypeSuffix']
35
- resolver: ResolverTs
36
- }): {
30
+ export function getEnumNames({ node, enum: enumOptions, resolver }: { node: ast.EnumSchemaNode; enum: EnumOptions; resolver: ResolverTs }): {
37
31
  enumName: string
38
32
  typeName: string
39
33
  } {
40
34
  const resolved = resolver.default(node.name!, 'type')
41
- const enumName = enumType === 'asPascalConst' ? resolved : camelCase(node.name!)
42
- const typeName = ENUM_TYPES_WITH_KEY_SUFFIX.has(enumType) ? resolver.resolveEnumKeyName(node, enumTypeSuffix) : resolved
35
+ const enumName = enumOptions.constCasing === 'pascalCase' ? resolved : camelCase(node.name!)
36
+ const typeName = ENUM_TYPES_WITH_KEY_SUFFIX.has(enumOptions.type) ? resolver.resolveEnumKeyName(node, enumOptions.typeSuffix) : resolved
43
37
 
44
38
  return { enumName, typeName }
45
39
  }
@@ -47,16 +41,16 @@ export function getEnumNames({
47
41
  /**
48
42
  * Renders the enum declaration(s) for a single named `EnumSchemaNode`.
49
43
  *
50
- * Depending on `enumType` this may emit:
51
- * - A runtime object (`asConst` / `asPascalConst`) plus a `typeof` type alias
44
+ * Depending on `enum.type` this may emit:
45
+ * - A runtime object (`asConst`) plus a `typeof` type alias
52
46
  * - A `const enum` or plain `enum` declaration (`constEnum` / `enum`)
53
47
  * - A union literal type alias (`literal`)
54
48
  *
55
49
  * The emitted `File.Source` nodes carry the resolved names so that the barrel
56
50
  * index picks up the correct export identifiers.
57
51
  */
58
- export function Enum({ node, enumType, enumTypeSuffix, enumKeyCasing, resolver }: Props): KubbReactNode {
59
- const { enumName, typeName } = getEnumNames({ node, enumType, enumTypeSuffix, resolver })
52
+ export function Enum({ node, enum: enumOptions, resolver }: Props): KubbReactNode {
53
+ const { enumName, typeName } = getEnumNames({ node, enum: enumOptions, resolver })
60
54
 
61
55
  const [nameNode, typeNode] = factory.createEnumDeclaration({
62
56
  name: enumName,
@@ -64,10 +58,15 @@ export function Enum({ node, enumType, enumTypeSuffix, enumKeyCasing, resolver }
64
58
  enums: (node.namedEnumValues?.map((v) => [trimQuotes(v.name.toString()), v.value]) ??
65
59
  node.enumValues?.filter((v): v is NonNullable<typeof v> => v !== null && v !== undefined).map((v) => [trimQuotes(v.toString()), v]) ??
66
60
  []) as Array<[string | number, string | number | boolean]>,
67
- type: enumType,
68
- enumKeyCasing,
61
+ type: enumOptions.type,
62
+ enumKeyCasing: enumOptions.keyCasing,
69
63
  })
70
64
 
65
+ // When the const and the type share a name (pascalCase const + empty typeSuffix) they merge into
66
+ // one declaration. The const carries the barrel export, so keep the type alias in the file but out
67
+ // of the barrel to avoid re-exporting the name a second time as `export type { … }`.
68
+ const namesMerge = !!nameNode && enumName === typeName
69
+
71
70
  return (
72
71
  <>
73
72
  {nameNode && (
@@ -75,7 +74,12 @@ export function Enum({ node, enumType, enumTypeSuffix, enumKeyCasing, resolver }
75
74
  {parserTs.print(nameNode)}
76
75
  </File.Source>
77
76
  )}
78
- <File.Source name={typeName} isIndexable isExportable={ENUM_TYPES_WITH_RUNTIME_VALUE.has(enumType)} isTypeOnly={ENUM_TYPES_WITH_TYPE_ONLY.has(enumType)}>
77
+ <File.Source
78
+ name={typeName}
79
+ isIndexable={!namesMerge}
80
+ isExportable={!namesMerge && ENUM_TYPES_WITH_RUNTIME_VALUE.has(enumOptions.type)}
81
+ isTypeOnly={ENUM_TYPES_WITH_TYPE_ONLY.has(enumOptions.type)}
82
+ >
79
83
  {parserTs.print(typeNode)}
80
84
  </File.Source>
81
85
  </>
@@ -13,13 +13,11 @@ type Props = {
13
13
  * Created with `printerTs({ ..., nodes: options.printer?.nodes })`.
14
14
  */
15
15
  printer: ast.Printer<PrinterTsFactory>
16
- enumType: PluginTs['resolvedOptions']['enumType']
17
- enumTypeSuffix: PluginTs['resolvedOptions']['enumTypeSuffix']
18
- enumKeyCasing: PluginTs['resolvedOptions']['enumKeyCasing']
16
+ enum: PluginTs['resolvedOptions']['enum']
19
17
  resolver: ResolverTs
20
18
  }
21
19
 
22
- export function Type({ name, node, printer, enumType, enumTypeSuffix, enumKeyCasing, resolver }: Props): KubbReactNode {
20
+ export function Type({ name, node, printer, enum: enumOptions, resolver }: Props): KubbReactNode {
23
21
  const enumSchemaNodes = ast.collect<ast.EnumSchemaNode>(node, {
24
22
  schema(n): ast.EnumSchemaNode | undefined {
25
23
  const enumNode = ast.narrowSchema(n, ast.schemaTypes.enum)
@@ -36,20 +34,17 @@ export function Type({ name, node, printer, enumType, enumTypeSuffix, enumKeyCas
36
34
  const enums = [...new Map(enumSchemaNodes.map((n) => [n.name, n])).values()].map((node) => {
37
35
  return {
38
36
  node,
39
- ...getEnumNames({ node, enumType, enumTypeSuffix, resolver }),
37
+ ...getEnumNames({ node, enum: enumOptions, resolver }),
40
38
  }
41
39
  })
42
40
 
43
41
  // Skip enum exports when using inlineLiteral
44
- const shouldExportEnums = enumType !== 'inlineLiteral'
45
- const shouldExportType = enumType === 'inlineLiteral' || enums.every((item) => item.typeName !== name)
42
+ const shouldExportEnums = enumOptions.type !== 'inlineLiteral'
43
+ const shouldExportType = enumOptions.type === 'inlineLiteral' || enums.every((item) => item.typeName !== name)
46
44
 
47
45
  return (
48
46
  <>
49
- {shouldExportEnums &&
50
- enums.map(({ node }) => (
51
- <Enum key={node.name} node={node} enumType={enumType} enumTypeSuffix={enumTypeSuffix} enumKeyCasing={enumKeyCasing} resolver={resolver} />
52
- ))}
47
+ {shouldExportEnums && enums.map(({ node }) => <Enum key={node.name} node={node} enum={enumOptions} resolver={resolver} />)}
53
48
  {shouldExportType && (
54
49
  <File.Source name={name} isTypeOnly isExportable isIndexable>
55
50
  {output}
package/src/constants.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import type { PluginTs } from './types.ts'
2
2
 
3
3
  type OptionalType = PluginTs['resolvedOptions']['optionalType']
4
- type EnumType = PluginTs['resolvedOptions']['enumType']
4
+ type EnumType = PluginTs['resolvedOptions']['enum']['type']
5
5
 
6
6
  /**
7
7
  * `optionalType` values that cause a property's type to include `| undefined`.
@@ -14,19 +14,19 @@ export const OPTIONAL_ADDS_UNDEFINED = new Set<OptionalType>(['undefined', 'ques
14
14
  export const OPTIONAL_ADDS_QUESTION_TOKEN = new Set<OptionalType>(['questionToken', 'questionTokenAndUndefined'] as const)
15
15
 
16
16
  /**
17
- * `enumType` values that append a `Key` suffix to the generated enum type alias.
17
+ * `enum.type` values that append a `typeSuffix` to the generated enum type alias.
18
18
  */
19
- export const ENUM_TYPES_WITH_KEY_SUFFIX = new Set<EnumType>(['asConst', 'asPascalConst'] as const)
19
+ export const ENUM_TYPES_WITH_KEY_SUFFIX = new Set<EnumType>(['asConst'] as const)
20
20
 
21
21
  /**
22
- * `enumType` values that require a runtime value declaration (object, enum, or literal).
22
+ * `enum.type` values that require a runtime value declaration (object, enum, or literal).
23
23
  */
24
- export const ENUM_TYPES_WITH_RUNTIME_VALUE = new Set<EnumType | undefined>(['enum', 'asConst', 'asPascalConst', 'constEnum', 'literal', undefined] as const)
24
+ export const ENUM_TYPES_WITH_RUNTIME_VALUE = new Set<EnumType | undefined>(['enum', 'asConst', 'constEnum', 'literal', undefined] as const)
25
25
 
26
26
  /**
27
- * `enumType` values whose type declaration is type-only (no runtime value emitted for the type alias).
27
+ * `enum.type` values whose type declaration is type-only (no runtime value emitted for the type alias).
28
28
  */
29
- export const ENUM_TYPES_WITH_TYPE_ONLY = new Set<EnumType | undefined>(['asConst', 'asPascalConst', 'literal', undefined] as const)
29
+ export const ENUM_TYPES_WITH_TYPE_ONLY = new Set<EnumType | undefined>(['asConst', 'literal', undefined] as const)
30
30
 
31
31
  /**
32
32
  * Ordering priority for function parameters: lower = sorted earlier.
package/src/factory.ts CHANGED
@@ -361,18 +361,6 @@ export function createTypeDeclaration({
361
361
  })
362
362
  }
363
363
 
364
- /**
365
- * Creates a TypeScript namespace declaration (exported module).
366
- */
367
- export function createNamespaceDeclaration({ statements, name }: { name: string; statements: Array<ts.Statement> }) {
368
- return factory.createModuleDeclaration(
369
- [factory.createToken(ts.SyntaxKind.ExportKeyword)],
370
- factory.createIdentifier(name),
371
- factory.createModuleBlock(statements),
372
- ts.NodeFlags.Namespace,
373
- )
374
- }
375
-
376
364
  /**
377
365
  * Creates an import declaration with support for default imports, named imports, namespace imports, and type-only imports.
378
366
  * Optionally rename imported members with `propertyName` and `name` pairs.
@@ -528,15 +516,14 @@ export function createEnumDeclaration({
528
516
  enumKeyCasing = 'none',
529
517
  }: {
530
518
  /**
531
- * Choose to use `enum`, `asConst`, `asPascalConst`, `constEnum`, or `literal` for enums.
519
+ * Choose to use `enum`, `asConst`, `constEnum`, or `literal` for enums.
532
520
  * - `enum`: TypeScript enum
533
- * - `asConst`: const with camelCase name (e.g., `petType`)
534
- * - `asPascalConst`: const with PascalCase name (e.g., `PetType`)
521
+ * - `asConst`: const object asserted with `as const` (the caller decides the const name casing)
535
522
  * - `constEnum`: const enum
536
523
  * - `literal`: literal union type
537
524
  * @default `'enum'`
538
525
  */
539
- type?: 'enum' | 'asConst' | 'asPascalConst' | 'constEnum' | 'literal' | 'inlineLiteral'
526
+ type?: 'enum' | 'asConst' | 'constEnum' | 'literal' | 'inlineLiteral'
540
527
  /**
541
528
  * Enum name in camelCase.
542
529
  */
@@ -629,8 +616,8 @@ export function createEnumDeclaration({
629
616
  }
630
617
 
631
618
  // used when using `as const` instead of an TypeScript enum.
632
- // name is already PascalCase for asPascalConst and camelCase for asConst (set in Type.tsx)
633
- // typeName has the Key suffix for type alias, so we use name for the const identifier
619
+ // name is already cased by the caller (camelCase or pascalCase, driven by enum.constCasing).
620
+ // typeName carries the typeSuffix for the type alias, so we use name for the const identifier.
634
621
  const identifierName = name
635
622
 
636
623
  // When there are no enum items (empty or all-null enum), don't generate a runtime const.
@@ -816,21 +803,11 @@ export const createStringLiteral = factory.createStringLiteral
816
803
  */
817
804
  export const createArrayTypeNode = factory.createArrayTypeNode
818
805
 
819
- /**
820
- * Creates a parenthesized type node to control operator precedence.
821
- */
822
- export const createParenthesizedType = factory.createParenthesizedType
823
-
824
806
  /**
825
807
  * Creates a literal type node (e.g., `'hello'`, `42`, `true`).
826
808
  */
827
809
  export const createLiteralTypeNode = factory.createLiteralTypeNode
828
810
 
829
- /**
830
- * Creates a null literal type node.
831
- */
832
- export const createNull = factory.createNull
833
-
834
811
  /**
835
812
  * Creates an identifier node.
836
813
  */
@@ -861,16 +838,6 @@ export const createTrue = factory.createTrue
861
838
  */
862
839
  export const createFalse = factory.createFalse
863
840
 
864
- /**
865
- * Creates an indexed access type node (e.g., `T[K]`).
866
- */
867
- export const createIndexedAccessTypeNode = factory.createIndexedAccessTypeNode
868
-
869
- /**
870
- * Creates a type operator node (e.g., `keyof T`, `readonly T[]`).
871
- */
872
- export const createTypeOperatorNode = factory.createTypeOperatorNode
873
-
874
841
  /**
875
842
  * Creates a prefix unary expression (e.g., negative numbers, logical not).
876
843
  */
@@ -1,6 +1,6 @@
1
1
  import { resolveContentTypeVariants } from '@internals/shared'
2
2
  import { ast, defineGenerator } from '@kubb/core'
3
- import { File, jsxRendererSync } from '@kubb/renderer-jsx'
3
+ import { File, jsxRenderer } from '@kubb/renderer-jsx'
4
4
  import { Type } from '../components/Type.tsx'
5
5
  import { ENUM_TYPES_WITH_KEY_SUFFIX } from '../constants.ts'
6
6
  import { printerTs } from '../printers/printerTs.ts'
@@ -15,22 +15,21 @@ import { buildData, buildResponses, buildResponseUnion } from '../utils.ts'
15
15
  */
16
16
  export const typeGenerator = defineGenerator<PluginTs>({
17
17
  name: 'typescript',
18
- renderer: jsxRendererSync,
18
+ renderer: jsxRenderer,
19
19
  schema(node, ctx) {
20
- const { enumType, enumTypeSuffix, enumKeyCasing, syntaxType, optionalType, arrayType, output, group, printer } = ctx.options
20
+ const { enum: enumOptions, syntaxType, optionalType, arrayType, output, group, printer } = ctx.options
21
21
  const { adapter, config, resolver, root } = ctx
22
22
 
23
23
  if (!node.name) {
24
24
  return
25
25
  }
26
- const mode = ctx.getMode(output)
27
26
  // Build a set of schema names that are enums so the ref handler and getImports
28
27
  // callback can use the suffixed type name (e.g. `StatusKey`) for those refs.
29
28
  const enumSchemaNames = new Set<string>(ctx.meta.enumNames)
30
29
 
31
30
  function resolveImportName(schemaName: string): string {
32
- if (ENUM_TYPES_WITH_KEY_SUFFIX.has(enumType) && enumTypeSuffix && enumSchemaNames.has(schemaName)) {
33
- return resolver.resolveEnumKeyName({ name: schemaName }, enumTypeSuffix)
31
+ if (ENUM_TYPES_WITH_KEY_SUFFIX.has(enumOptions.type) && enumOptions.typeSuffix && enumSchemaNames.has(schemaName)) {
32
+ return resolver.resolveEnumKeyName({ name: schemaName }, enumOptions.typeSuffix)
34
33
  }
35
34
  return resolver.resolveTypeName(schemaName)
36
35
  }
@@ -43,15 +42,17 @@ export const typeGenerator = defineGenerator<PluginTs>({
43
42
  const isEnumSchema = !!ast.narrowSchema(node, ast.schemaTypes.enum)
44
43
 
45
44
  const meta = {
46
- name: ENUM_TYPES_WITH_KEY_SUFFIX.has(enumType) && isEnumSchema ? resolver.resolveEnumKeyName(node, enumTypeSuffix) : resolver.resolveTypeName(node.name),
45
+ name:
46
+ ENUM_TYPES_WITH_KEY_SUFFIX.has(enumOptions.type) && isEnumSchema
47
+ ? resolver.resolveEnumKeyName(node, enumOptions.typeSuffix)
48
+ : resolver.resolveTypeName(node.name),
47
49
  file: resolver.resolveFile({ name: node.name, extname: '.ts' }, { root, output, group: group ?? undefined }),
48
50
  } as const
49
51
 
50
52
  const schemaPrinter = printerTs({
51
53
  optionalType,
52
54
  arrayType,
53
- enumType,
54
- enumTypeSuffix,
55
+ enum: enumOptions,
55
56
  name: meta.name,
56
57
  syntaxType,
57
58
  description: node.description,
@@ -68,28 +69,17 @@ export const typeGenerator = defineGenerator<PluginTs>({
68
69
  banner={resolver.resolveBanner(ctx.meta, { output, config, file: { path: meta.file.path, baseName: meta.file.baseName } })}
69
70
  footer={resolver.resolveFooter(ctx.meta, { output, config, file: { path: meta.file.path, baseName: meta.file.baseName } })}
70
71
  >
71
- {mode === 'split' &&
72
- imports.map((imp) => (
73
- <File.Import key={[node.name, imp.path, imp.isTypeOnly].join('-')} root={meta.file.path} path={imp.path} name={imp.name} isTypeOnly />
74
- ))}
75
- <Type
76
- name={meta.name}
77
- node={node}
78
- enumType={enumType}
79
- enumTypeSuffix={enumTypeSuffix}
80
- enumKeyCasing={enumKeyCasing}
81
- resolver={resolver}
82
- printer={schemaPrinter}
83
- />
72
+ {imports.map((imp) => (
73
+ <File.Import key={[node.name, imp.path, imp.isTypeOnly].join('-')} root={meta.file.path} path={imp.path} name={imp.name} isTypeOnly />
74
+ ))}
75
+ <Type name={meta.name} node={node} enum={enumOptions} resolver={resolver} printer={schemaPrinter} />
84
76
  </File>
85
77
  )
86
78
  },
87
79
  operation(node, ctx) {
88
- const { enumType, enumTypeSuffix, enumKeyCasing, optionalType, arrayType, syntaxType, paramsCasing, group, output, printer } = ctx.options
80
+ const { enum: enumOptions, optionalType, arrayType, syntaxType, paramsCasing, group, output, printer } = ctx.options
89
81
  const { adapter, config, resolver, root } = ctx
90
82
 
91
- const mode = ctx.getMode(output)
92
-
93
83
  const params = ast.caseParams(node.parameters, paramsCasing)
94
84
 
95
85
  const meta = {
@@ -104,8 +94,8 @@ export const typeGenerator = defineGenerator<PluginTs>({
104
94
  const enumSchemaNames = new Set<string>(ctx.meta.enumNames)
105
95
 
106
96
  function resolveImportName(schemaName: string): string {
107
- if (ENUM_TYPES_WITH_KEY_SUFFIX.has(enumType) && enumTypeSuffix && enumSchemaNames.has(schemaName)) {
108
- return resolver.resolveEnumKeyName({ name: schemaName }, enumTypeSuffix)
97
+ if (ENUM_TYPES_WITH_KEY_SUFFIX.has(enumOptions.type) && enumOptions.typeSuffix && enumSchemaNames.has(schemaName)) {
98
+ return resolver.resolveEnumKeyName({ name: schemaName }, enumOptions.typeSuffix)
109
99
  }
110
100
  return resolver.resolveTypeName(schemaName)
111
101
  }
@@ -121,8 +111,7 @@ export const typeGenerator = defineGenerator<PluginTs>({
121
111
  const schemaPrinter = printerTs({
122
112
  optionalType,
123
113
  arrayType,
124
- enumType,
125
- enumTypeSuffix,
114
+ enum: enumOptions,
126
115
  name,
127
116
  syntaxType,
128
117
  description: schema.description,
@@ -134,19 +123,10 @@ export const typeGenerator = defineGenerator<PluginTs>({
134
123
 
135
124
  return (
136
125
  <>
137
- {mode === 'split' &&
138
- imports.map((imp) => (
139
- <File.Import key={[name, imp.path, imp.isTypeOnly].join('-')} root={meta.file.path} path={imp.path} name={imp.name} isTypeOnly />
140
- ))}
141
- <Type
142
- name={name}
143
- node={schema}
144
- enumType={enumType}
145
- enumTypeSuffix={enumTypeSuffix}
146
- enumKeyCasing={enumKeyCasing}
147
- resolver={resolver}
148
- printer={schemaPrinter}
149
- />
126
+ {imports.map((imp) => (
127
+ <File.Import key={[name, imp.path, imp.isTypeOnly].join('-')} root={meta.file.path} path={imp.path} name={imp.name} isTypeOnly />
128
+ ))}
129
+ <Type name={name} node={schema} enum={enumOptions} resolver={resolver} printer={schemaPrinter} />
150
130
  </>
151
131
  )
152
132
  }
package/src/plugin.ts CHANGED
@@ -27,7 +27,7 @@ export const pluginTsName = 'plugin-ts' satisfies PluginTs['name']
27
27
  * plugins: [
28
28
  * pluginTs({
29
29
  * output: { path: './types' },
30
- * enumType: 'asConst',
30
+ * enum: { type: 'asConst' },
31
31
  * optionalType: 'questionTokenAndUndefined',
32
32
  * }),
33
33
  * ],
@@ -36,14 +36,12 @@ export const pluginTsName = 'plugin-ts' satisfies PluginTs['name']
36
36
  */
37
37
  export const pluginTs = definePlugin<PluginTs>((options) => {
38
38
  const {
39
- output = { path: 'types', barrelType: 'named' },
39
+ output = { path: 'types', barrel: { type: 'named' } },
40
40
  group,
41
41
  exclude = [],
42
42
  include,
43
43
  override = [],
44
- enumType = 'asConst',
45
- enumTypeSuffix = 'Key',
46
- enumKeyCasing = 'none',
44
+ enum: enumOptions = {},
47
45
  optionalType = 'questionToken',
48
46
  arrayType = 'array',
49
47
  syntaxType = 'type',
@@ -54,7 +52,14 @@ export const pluginTs = definePlugin<PluginTs>((options) => {
54
52
  generators: userGenerators = [],
55
53
  } = options
56
54
 
57
- const groupConfig = createGroupConfig(group, { suffix: 'Controller' })
55
+ const groupConfig = createGroupConfig(group)
56
+
57
+ const resolvedEnum = {
58
+ type: enumOptions.type ?? 'asConst',
59
+ constCasing: enumOptions.constCasing ?? 'camelCase',
60
+ typeSuffix: enumOptions.typeSuffix ?? 'Key',
61
+ keyCasing: enumOptions.keyCasing ?? 'none',
62
+ }
58
63
 
59
64
  return {
60
65
  name: pluginTsName,
@@ -69,9 +74,7 @@ export const pluginTs = definePlugin<PluginTs>((options) => {
69
74
  optionalType,
70
75
  group: groupConfig,
71
76
  arrayType,
72
- enumType,
73
- enumTypeSuffix,
74
- enumKeyCasing,
77
+ enum: resolvedEnum,
75
78
  syntaxType,
76
79
  paramsCasing,
77
80
  printer,
@@ -1,3 +1,4 @@
1
+ import { extractRefName } from '@kubb/ast/utils'
1
2
  import { ast } from '@kubb/core'
2
3
  import { parserTs } from '@kubb/parser-ts'
3
4
  import type ts from 'typescript'
@@ -50,23 +51,11 @@ export type PrinterTsOptions = {
50
51
  */
51
52
  arrayType: PluginTs['resolvedOptions']['arrayType']
52
53
  /**
53
- * Enum output format.
54
- * - `'inlineLiteral'` embeds literal unions inline
55
- * - `'asPascalConst'` generates named const unions
56
- * - `'asConst'` generates as const declarations
57
- *
58
- * @default `'inlineLiteral'`
59
- */
60
- enumType: PluginTs['resolvedOptions']['enumType']
61
- /**
62
- * Suffix appended to enum key reference names.
63
- *
64
- * @example Enum key naming
65
- * `StatusKey` when `enumType` is `'asConst'`
66
- *
67
- * @default `'Key'`
54
+ * Grouped enum settings. The printer emits references to enums, not the enum declarations, so only
55
+ * `type` (the output format) and `typeSuffix` (the enum key reference suffix) matter here.
56
+ * `constCasing` and `keyCasing` are ignored.
68
57
  */
69
- enumTypeSuffix?: PluginTs['resolvedOptions']['enumTypeSuffix']
58
+ enum: PluginTs['resolvedOptions']['enum']
70
59
  /**
71
60
  * Syntax for generated declarations.
72
61
  * - `'type'` generates type aliases
@@ -124,13 +113,13 @@ type PrinterTs = PrinterTsFactory
124
113
  *
125
114
  * @example Raw type node (no `typeName`)
126
115
  * ```ts
127
- * const printer = printerTs({ optionalType: 'questionToken', arrayType: 'array', enumType: 'inlineLiteral' })
116
+ * const printer = printerTs({ optionalType: 'questionToken', arrayType: 'array', enum: { type: 'inlineLiteral' } })
128
117
  * const typeNode = printer.print(schemaNode) // ts.TypeNode
129
118
  * ```
130
119
  *
131
120
  * @example Full declaration (with `typeName`)
132
121
  * ```ts
133
- * const printer = printerTs({ optionalType: 'questionToken', arrayType: 'array', enumType: 'inlineLiteral', typeName: 'MyType' })
122
+ * const printer = printerTs({ optionalType: 'questionToken', arrayType: 'array', enum: { type: 'inlineLiteral' }, typeName: 'MyType' })
134
123
  * const declaration = printer.print(schemaNode) // ts.TypeAliasDeclaration | ts.InterfaceDeclaration
135
124
  * ```
136
125
  */
@@ -173,15 +162,15 @@ export const printerTs = ast.definePrinter<PrinterTs>((options) => {
173
162
  // Use the canonical name from the $ref path — node.name may have been overridden
174
163
  // (e.g. by single-member allOf flatten using the property-derived child name).
175
164
  // Inline refs (without $ref) from utils already carry resolved type names.
176
- const refName = node.ref ? (ast.extractRefName(node.ref) ?? node.name) : node.name
165
+ const refName = node.ref ? (extractRefName(node.ref) ?? node.name) : node.name
177
166
 
178
167
  // When a Key suffix is configured, enum refs must use the suffixed name (e.g. `StatusKey`)
179
168
  // so the reference matches what the enum file actually exports.
180
169
  const isEnumRef =
181
- node.ref && ENUM_TYPES_WITH_KEY_SUFFIX.has(this.options.enumType) && this.options.enumTypeSuffix && this.options.enumSchemaNames?.has(refName)
170
+ node.ref && ENUM_TYPES_WITH_KEY_SUFFIX.has(this.options.enum.type) && this.options.enum.typeSuffix && this.options.enumSchemaNames?.has(refName)
182
171
 
183
172
  const name = isEnumRef
184
- ? this.options.resolver.resolveEnumKeyName({ name: refName }, this.options.enumTypeSuffix!)
173
+ ? this.options.resolver.resolveEnumKeyName({ name: refName }, this.options.enum.typeSuffix)
185
174
  : node.ref
186
175
  ? this.options.resolver.default(refName, 'type')
187
176
  : refName
@@ -191,7 +180,7 @@ export const printerTs = ast.definePrinter<PrinterTs>((options) => {
191
180
  enum(node) {
192
181
  const values = node.namedEnumValues?.map((v) => v.value) ?? node.enumValues ?? []
193
182
 
194
- if (this.options.enumType === 'inlineLiteral' || !node.name) {
183
+ if (this.options.enum.type === 'inlineLiteral' || !node.name) {
195
184
  const literalNodes = values
196
185
  .filter((v): v is string | number | boolean => v !== null && v !== undefined)
197
186
  .map((value) => factory.constToTypeNode(value, typeof value as 'string' | 'number' | 'boolean'))
@@ -201,8 +190,8 @@ export const printerTs = ast.definePrinter<PrinterTs>((options) => {
201
190
  }
202
191
 
203
192
  const resolvedName =
204
- ENUM_TYPES_WITH_KEY_SUFFIX.has(this.options.enumType) && this.options.enumTypeSuffix
205
- ? this.options.resolver.resolveEnumKeyName(node, this.options.enumTypeSuffix)
193
+ ENUM_TYPES_WITH_KEY_SUFFIX.has(this.options.enum.type) && this.options.enum.typeSuffix
194
+ ? this.options.resolver.resolveEnumKeyName(node, this.options.enum.typeSuffix)
206
195
  : this.options.resolver.default(node.name, 'type')
207
196
 
208
197
  return factory.createTypeReferenceNode(resolvedName, undefined)
@@ -1,4 +1,4 @@
1
- import { ensureValidVarName, pascalCase } from '@internals/utils'
1
+ import { ensureValidVarName, pascalCase, toFilePath } from '@internals/utils'
2
2
  import { defineResolver } from '@kubb/core'
3
3
  import type { PluginTs } from '../types.ts'
4
4
 
@@ -9,7 +9,7 @@ import type { PluginTs } from '../types.ts'
9
9
  * casing/file-layout rules.
10
10
  *
11
11
  * The `default` method is supplied by `defineResolver`. It uses PascalCase for
12
- * type names and PascalCase-with-isFile for files.
12
+ * type names and PascalCase file paths (dotted names become `/`-joined) for files.
13
13
  *
14
14
  * @example Resolve a type and file name
15
15
  * ```ts
@@ -25,15 +25,15 @@ export const resolverTs = defineResolver<PluginTs>(() => {
25
25
  name: 'default',
26
26
  pluginName: 'plugin-ts',
27
27
  default(name, type) {
28
- const resolved = pascalCase(name, { isFile: type === 'file' })
29
- return type === 'file' ? resolved : ensureValidVarName(resolved)
28
+ if (type === 'file') return toFilePath(name, pascalCase)
29
+ return ensureValidVarName(pascalCase(name))
30
30
  },
31
31
  resolveTypeName(name) {
32
32
  return ensureValidVarName(pascalCase(name))
33
33
  },
34
34
  resolvePathName(name, type) {
35
- const resolved = pascalCase(name, { isFile: type === 'file' })
36
- return type === 'file' ? resolved : ensureValidVarName(resolved)
35
+ if (type === 'file') return toFilePath(name, pascalCase)
36
+ return ensureValidVarName(pascalCase(name))
37
37
  },
38
38
  resolveParamName(node, param) {
39
39
  return this.resolveTypeName(`${node.operationId} ${param.in} ${param.name}`)