@kubb/plugin-ts 5.0.0-alpha.8 → 5.0.0-beta.3

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.
Files changed (42) hide show
  1. package/LICENSE +17 -10
  2. package/README.md +1 -3
  3. package/dist/index.cjs +1452 -4
  4. package/dist/index.cjs.map +1 -0
  5. package/dist/index.d.ts +574 -4
  6. package/dist/index.js +1418 -2
  7. package/dist/index.js.map +1 -0
  8. package/package.json +44 -64
  9. package/src/components/{v2/Enum.tsx → Enum.tsx} +33 -17
  10. package/src/components/Type.tsx +31 -161
  11. package/src/constants.ts +15 -5
  12. package/src/factory.ts +283 -35
  13. package/src/generators/typeGenerator.tsx +189 -424
  14. package/src/index.ts +9 -2
  15. package/src/plugin.ts +67 -205
  16. package/src/printers/functionPrinter.ts +197 -0
  17. package/src/printers/printerTs.ts +325 -0
  18. package/src/resolvers/resolverTs.ts +66 -0
  19. package/src/types.ts +238 -94
  20. package/src/utils.ts +130 -0
  21. package/dist/components-CRu8IKY3.js +0 -729
  22. package/dist/components-CRu8IKY3.js.map +0 -1
  23. package/dist/components-DeNDKlzf.cjs +0 -982
  24. package/dist/components-DeNDKlzf.cjs.map +0 -1
  25. package/dist/components.cjs +0 -3
  26. package/dist/components.d.ts +0 -36
  27. package/dist/components.js +0 -2
  28. package/dist/generators.cjs +0 -4
  29. package/dist/generators.d.ts +0 -480
  30. package/dist/generators.js +0 -2
  31. package/dist/plugin-D5NGPj0v.js +0 -1232
  32. package/dist/plugin-D5NGPj0v.js.map +0 -1
  33. package/dist/plugin-MLTxoa8p.cjs +0 -1279
  34. package/dist/plugin-MLTxoa8p.cjs.map +0 -1
  35. package/dist/types-CsvB6X5Y.d.ts +0 -167
  36. package/src/components/index.ts +0 -1
  37. package/src/components/v2/Type.tsx +0 -59
  38. package/src/generators/index.ts +0 -2
  39. package/src/generators/v2/typeGenerator.tsx +0 -171
  40. package/src/generators/v2/utils.ts +0 -140
  41. package/src/parser.ts +0 -389
  42. package/src/printer.ts +0 -368
package/src/plugin.ts CHANGED
@@ -1,14 +1,31 @@
1
- import path from 'node:path'
2
- import { camelCase, pascalCase } from '@internals/utils'
3
- import { walk } from '@kubb/ast'
4
- import { createPlugin, type Group, getBarrelFiles, getMode, resolveOptions } from '@kubb/core'
5
- import { buildOperation, buildSchema, OperationGenerator, pluginOasName, SchemaGenerator } from '@kubb/plugin-oas'
6
- import { typeGenerator, typeGeneratorV2 } from './generators'
1
+ import { camelCase } from '@internals/utils'
2
+ import { definePlugin, type Group } from '@kubb/core'
3
+ import { typeGenerator } from './generators/typeGenerator.tsx'
4
+ import { resolverTs } from './resolvers/resolverTs.ts'
7
5
  import type { PluginTs } from './types.ts'
8
6
 
7
+ /**
8
+ * Canonical plugin name for `@kubb/plugin-ts`, used to identify the plugin in driver lookups and warnings.
9
+ */
9
10
  export const pluginTsName = 'plugin-ts' satisfies PluginTs['name']
10
11
 
11
- export const pluginTs = createPlugin<PluginTs>((options) => {
12
+ /**
13
+ * The `@kubb/plugin-ts` plugin factory.
14
+ *
15
+ * Generates TypeScript type declarations from an OpenAPI/AST `RootNode`.
16
+ * Walks schemas and operations, delegates rendering to the active generators,
17
+ * and writes barrel files based on `output.barrelType`.
18
+ *
19
+ * @example
20
+ * ```ts
21
+ * import pluginTs from '@kubb/plugin-ts'
22
+ *
23
+ * export default defineConfig({
24
+ * plugins: [pluginTs({ output: { path: 'types' }, enumType: 'asConst' })],
25
+ * })
26
+ * ```
27
+ */
28
+ export const pluginTs = definePlugin<PluginTs>((options) => {
12
29
  const {
13
30
  output = { path: 'types', barrelType: 'named' },
14
31
  group,
@@ -16,216 +33,61 @@ export const pluginTs = createPlugin<PluginTs>((options) => {
16
33
  include,
17
34
  override = [],
18
35
  enumType = 'asConst',
36
+ enumTypeSuffix = 'Key',
19
37
  enumKeyCasing = 'none',
20
- enumSuffix = 'enum',
21
- dateType = 'string',
22
- integerType = 'number',
23
- unknownType = 'any',
24
38
  optionalType = 'questionToken',
25
39
  arrayType = 'array',
26
- emptySchemaType = unknownType,
27
40
  syntaxType = 'type',
28
- transformers = {},
29
41
  paramsCasing,
30
- generators = [typeGenerator, typeGeneratorV2].filter(Boolean),
31
- contentType,
32
- UNSTABLE_NAMING,
42
+ printer,
43
+ resolver: userResolver,
44
+ transformer: userTransformer,
45
+ generators: userGenerators = [],
33
46
  } = options
34
47
 
35
- // @deprecated Will be removed in v5 when collisionDetection defaults to true
36
- const usedEnumNames = {}
48
+ const groupConfig = group
49
+ ? ({
50
+ ...group,
51
+ name: (ctx) => {
52
+ if (group.type === 'path') {
53
+ return `${ctx.group.split('/')[1]}`
54
+ }
55
+ return `${camelCase(ctx.group)}Controller`
56
+ },
57
+ } satisfies Group)
58
+ : undefined
37
59
 
38
60
  return {
39
61
  name: pluginTsName,
40
- options: {
41
- output,
42
- transformers,
43
- dateType,
44
- integerType,
45
- optionalType,
46
- arrayType,
47
- enumType,
48
- enumKeyCasing,
49
- enumSuffix,
50
- unknownType,
51
- emptySchemaType,
52
- syntaxType,
53
- group,
54
- override,
55
- paramsCasing,
56
- usedEnumNames,
57
- },
58
- pre: [pluginOasName],
59
- resolvePath(baseName, pathMode, options) {
60
- const root = path.resolve(this.config.root, this.config.output.path)
61
- const mode = pathMode ?? getMode(path.resolve(root, output.path))
62
-
63
- if (mode === 'single') {
64
- /**
65
- * when output is a file then we will always append to the same file(output file), see fileManager.addOrAppend
66
- * Other plugins then need to call addOrAppend instead of just add from the fileManager class
67
- */
68
- return path.resolve(root, output.path)
69
- }
70
-
71
- if (group && (options?.group?.path || options?.group?.tag)) {
72
- const groupName: Group['name'] = group?.name
73
- ? group.name
74
- : (ctx) => {
75
- if (group?.type === 'path') {
76
- return `${ctx.group.split('/')[1]}`
77
- }
78
- return `${camelCase(ctx.group)}Controller`
79
- }
80
-
81
- return path.resolve(
82
- root,
83
- output.path,
84
- groupName({
85
- group: group.type === 'path' ? options.group.path! : options.group.tag!,
86
- }),
87
- baseName,
88
- )
89
- }
90
-
91
- return path.resolve(root, output.path, baseName)
92
- },
93
- resolveName(name, type) {
94
- const resolvedName = pascalCase(name, { isFile: type === 'file' })
95
-
96
- if (type) {
97
- return transformers?.name?.(resolvedName, type) || resolvedName
98
- }
99
-
100
- return resolvedName
101
- },
102
- async install() {
103
- const { config, fabric, plugin, adapter, rootNode, driver, openInStudio } = this
104
-
105
- const root = path.resolve(config.root, config.output.path)
106
- const mode = getMode(path.resolve(root, output.path))
107
-
108
- if (adapter) {
109
- await openInStudio({ ast: true })
110
-
111
- await walk(
112
- rootNode,
113
- {
114
- async schema(schemaNode) {
115
- const writeTasks = generators.map(async (generator) => {
116
- if (generator.type === 'react' && generator.version === '2') {
117
- const options = resolveOptions(schemaNode, { options: plugin.options, exclude, include, override })
118
-
119
- if (options === null) {
120
- return
121
- }
122
-
123
- await buildSchema(schemaNode, {
124
- options,
125
- adapter,
126
- config,
127
- fabric,
128
- Component: generator.Schema,
129
- plugin,
130
- driver,
131
- mode,
132
- version: generator.version,
133
- })
134
- }
135
- })
136
-
137
- await Promise.all(writeTasks)
138
- },
139
- async operation(operationNode) {
140
- const writeTasks = generators.map(async (generator) => {
141
- if (generator.type === 'react' && generator.version === '2') {
142
- const options = resolveOptions(operationNode, { options: plugin.options, exclude, include, override })
143
-
144
- if (options === null) {
145
- return
146
- }
147
-
148
- await buildOperation(operationNode, {
149
- options,
150
- adapter,
151
- config,
152
- fabric,
153
- Component: generator.Operation,
154
- plugin,
155
- driver,
156
- mode,
157
- version: generator.version,
158
- })
159
- }
160
- })
161
-
162
- await Promise.all(writeTasks)
163
- },
164
- },
165
- { depth: 'shallow' },
166
- )
167
-
168
- const barrelFiles = await getBarrelFiles(this.fabric.files, {
169
- type: output.barrelType ?? 'named',
170
- root,
62
+ options,
63
+ hooks: {
64
+ 'kubb:plugin:setup'(ctx) {
65
+ ctx.setOptions({
171
66
  output,
172
- meta: {
173
- pluginName: this.plugin.name,
174
- },
67
+ exclude,
68
+ include,
69
+ override,
70
+ optionalType,
71
+ group: groupConfig,
72
+ arrayType,
73
+ enumType,
74
+ enumTypeSuffix,
75
+ enumKeyCasing,
76
+ syntaxType,
77
+ paramsCasing,
78
+ printer,
175
79
  })
176
-
177
- await this.upsertFile(...barrelFiles)
178
-
179
- return
180
- }
181
-
182
- // v1 flow
183
-
184
- const oas = await this.getOas()
185
-
186
- const schemaGenerator = new SchemaGenerator(this.plugin.options, {
187
- fabric: this.fabric,
188
- oas,
189
- driver: this.driver,
190
- events: this.events,
191
- plugin: this.plugin,
192
- contentType,
193
- include: undefined,
194
- override,
195
- mode,
196
- output: output.path,
197
- })
198
-
199
- const schemaFiles = await schemaGenerator.build(...generators)
200
- await this.upsertFile(...schemaFiles)
201
-
202
- const operationGenerator = new OperationGenerator(this.plugin.options, {
203
- fabric: this.fabric,
204
- oas,
205
- driver: this.driver,
206
- events: this.events,
207
- plugin: this.plugin,
208
- contentType,
209
- exclude,
210
- include,
211
- override,
212
- mode,
213
- UNSTABLE_NAMING,
214
- })
215
-
216
- const operationFiles = await operationGenerator.build(...generators)
217
- await this.upsertFile(...operationFiles)
218
-
219
- const barrelFiles = await getBarrelFiles(this.fabric.files, {
220
- type: output.barrelType ?? 'named',
221
- root,
222
- output,
223
- meta: {
224
- pluginName: this.plugin.name,
225
- },
226
- })
227
-
228
- await this.upsertFile(...barrelFiles)
80
+ ctx.setResolver(userResolver ? { ...resolverTs, ...userResolver } : resolverTs)
81
+ if (userTransformer) {
82
+ ctx.setTransformer(userTransformer)
83
+ }
84
+ ctx.addGenerator(typeGenerator)
85
+ for (const gen of userGenerators) {
86
+ ctx.addGenerator(gen)
87
+ }
88
+ },
229
89
  },
230
90
  }
231
91
  })
92
+
93
+ export default pluginTs
@@ -0,0 +1,197 @@
1
+ import { ast } from '@kubb/core'
2
+ import { PARAM_RANK } from '../constants.ts'
3
+
4
+ /**
5
+ * Maps each function-printer handler key to its concrete node type.
6
+ */
7
+ export type FunctionNodeByType = {
8
+ functionParameter: ast.FunctionParameterNode
9
+ parameterGroup: ast.ParameterGroupNode
10
+ functionParameters: ast.FunctionParametersNode
11
+ paramsType: ast.ParamsTypeNode
12
+ }
13
+
14
+ const kindToHandlerKey = {
15
+ FunctionParameter: 'functionParameter',
16
+ ParameterGroup: 'parameterGroup',
17
+ FunctionParameters: 'functionParameters',
18
+ ParamsType: 'paramsType',
19
+ } satisfies Record<string, ast.FunctionNodeType>
20
+
21
+ /**
22
+ * Creates a function-parameter printer factory.
23
+ *
24
+ * Uses `createPrinterFactory` and dispatches handlers by `node.kind`
25
+ * (for function nodes) rather than by `node.type` (for schema nodes).
26
+ */
27
+ export const defineFunctionPrinter = ast.createPrinterFactory<ast.FunctionParamNode, ast.FunctionNodeType, FunctionNodeByType>(
28
+ (node) => kindToHandlerKey[node.kind],
29
+ )
30
+
31
+ export type FunctionPrinterOptions = {
32
+ /**
33
+ * Rendering modes supported by `functionPrinter`.
34
+ *
35
+ * | Mode | Output example | Use case |
36
+ * |---------------|---------------------------------------------|---------------------------------|
37
+ * | `declaration` | `id: string, config: Config = {}` | Function parameter declaration |
38
+ * | `call` | `id, { method, url }` | Function call arguments |
39
+ * | `keys` | `{ id, config }` | Key names only (destructuring) |
40
+ * | `values` | `{ id: id, config: config }` | Key/value object entries |
41
+ */
42
+ mode: 'declaration' | 'call' | 'keys' | 'values'
43
+ /**
44
+ * Optional transformation applied to every parameter name before printing.
45
+ */
46
+ transformName?: (name: string) => string
47
+ /**
48
+ * Optional transformation applied to every type string before printing.
49
+ */
50
+ transformType?: (type: string) => string
51
+ }
52
+
53
+ type DefaultPrinter = ast.PrinterFactoryOptions<'functionParameters', FunctionPrinterOptions, string>
54
+
55
+ function rank(param: ast.FunctionParameterNode | ast.ParameterGroupNode): number {
56
+ if (param.kind === 'ParameterGroup') {
57
+ if (param.default) return PARAM_RANK.withDefault
58
+ const isOptional = param.optional ?? param.properties.every((p) => p.optional || p.default !== undefined)
59
+ return isOptional ? PARAM_RANK.optional : PARAM_RANK.required
60
+ }
61
+ if (param.rest) return PARAM_RANK.rest
62
+ if (param.default) return PARAM_RANK.withDefault
63
+ return param.optional ? PARAM_RANK.optional : PARAM_RANK.required
64
+ }
65
+
66
+ function sortParams(params: ReadonlyArray<ast.FunctionParameterNode | ast.ParameterGroupNode>): Array<ast.FunctionParameterNode | ast.ParameterGroupNode> {
67
+ return [...params].sort((a, b) => rank(a) - rank(b))
68
+ }
69
+
70
+ function sortChildParams(params: Array<ast.FunctionParameterNode>): Array<ast.FunctionParameterNode> {
71
+ return [...params].sort((a, b) => rank(a) - rank(b))
72
+ }
73
+
74
+ /**
75
+ * Default function-signature printer.
76
+ * Covers the four standard output modes used across Kubb plugins.
77
+ *
78
+ * @example
79
+ * ```ts
80
+ * const printer = functionPrinter({ mode: 'declaration' })
81
+ *
82
+ * const sig = createFunctionParameters({
83
+ * params: [
84
+ * createFunctionParameter({ name: 'petId', type: 'string', optional: false }),
85
+ * createFunctionParameter({ name: 'config', type: 'Config', optional: false, default: '{}' }),
86
+ * ],
87
+ * })
88
+ *
89
+ * printer.print(sig) // → "petId: string, config: Config = {}"
90
+ * ```
91
+ */
92
+ export const functionPrinter = defineFunctionPrinter<DefaultPrinter>((options) => ({
93
+ name: 'functionParameters',
94
+ options,
95
+ nodes: {
96
+ paramsType(node) {
97
+ if (node.kind !== 'ParamsType') return null
98
+ if (node.variant === 'member') {
99
+ return `${node.base}['${node.key}']`
100
+ }
101
+ if (node.variant === 'struct') {
102
+ const parts = node.properties.map((p) => {
103
+ const typeStr = this.transform(p.type)
104
+ const key = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(p.name) ? p.name : JSON.stringify(p.name)
105
+ return p.optional ? `${key}?: ${typeStr}` : `${key}: ${typeStr}`
106
+ })
107
+ return `{ ${parts.join('; ')} }`
108
+ }
109
+ if (node.variant === 'reference') {
110
+ return node.name
111
+ }
112
+ return null
113
+ },
114
+ functionParameter(node) {
115
+ const { mode, transformName, transformType } = this.options
116
+ const name = transformName ? transformName(node.name) : node.name
117
+
118
+ const rawType = node.type ? this.transform(node.type) : undefined
119
+ const type = rawType != null && transformType ? transformType(rawType) : rawType
120
+
121
+ if (mode === 'keys' || mode === 'values') {
122
+ return node.rest ? `...${name}` : name
123
+ }
124
+
125
+ if (mode === 'call') {
126
+ return node.rest ? `...${name}` : name
127
+ }
128
+
129
+ if (node.rest) {
130
+ return type ? `...${name}: ${type}` : `...${name}`
131
+ }
132
+ if (type) {
133
+ if (node.optional) return `${name}?: ${type}`
134
+ return node.default ? `${name}: ${type} = ${node.default}` : `${name}: ${type}`
135
+ }
136
+ return node.default ? `${name} = ${node.default}` : name
137
+ },
138
+ parameterGroup(node) {
139
+ const { mode, transformName, transformType } = this.options
140
+ const sorted = sortChildParams(node.properties)
141
+ const isOptional = node.optional ?? sorted.every((p) => p.optional || p.default !== undefined)
142
+
143
+ if (node.inline) {
144
+ return sorted
145
+ .map((p) => this.transform(p))
146
+ .filter(Boolean)
147
+ .join(', ')
148
+ }
149
+
150
+ if (mode === 'keys' || mode === 'values') {
151
+ const keys = sorted.map((p) => p.name).join(', ')
152
+ return `{ ${keys} }`
153
+ }
154
+
155
+ if (mode === 'call') {
156
+ const keys = sorted.map((p) => p.name).join(', ')
157
+ return `{ ${keys} }`
158
+ }
159
+
160
+ const names = sorted.map((p) => {
161
+ const n = transformName ? transformName(p.name) : p.name
162
+
163
+ return n
164
+ })
165
+
166
+ const nameStr = names.length ? `{ ${names.join(', ')} }` : undefined
167
+ if (!nameStr) return null
168
+
169
+ let typeAnnotation: string | undefined = node.type ? (this.transform(node.type) ?? undefined) : undefined
170
+ if (!typeAnnotation) {
171
+ const typeParts = sorted
172
+ .filter((p) => p.type)
173
+ .map((p) => {
174
+ const rawT = p.type ? this.transform(p.type) : undefined
175
+ const t = rawT != null && transformType ? transformType(rawT) : rawT
176
+ return p.optional || p.default !== undefined ? `${p.name}?: ${t}` : `${p.name}: ${t}`
177
+ })
178
+ typeAnnotation = typeParts.length ? `{ ${typeParts.join('; ')} }` : undefined
179
+ }
180
+
181
+ if (typeAnnotation) {
182
+ if (isOptional) return `${nameStr}: ${typeAnnotation} = ${node.default ?? '{}'}`
183
+ return node.default ? `${nameStr}: ${typeAnnotation} = ${node.default}` : `${nameStr}: ${typeAnnotation}`
184
+ }
185
+
186
+ return node.default ? `${nameStr} = ${node.default}` : nameStr
187
+ },
188
+ functionParameters(node) {
189
+ const sorted = sortParams(node.params)
190
+
191
+ return sorted
192
+ .map((p) => this.transform(p))
193
+ .filter(Boolean)
194
+ .join(', ')
195
+ },
196
+ },
197
+ }))