@kubb/core 5.0.0-beta.2 → 5.0.0-beta.21

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 (45) hide show
  1. package/README.md +8 -38
  2. package/dist/KubbDriver-BBRa5CH2.cjs +2231 -0
  3. package/dist/KubbDriver-BBRa5CH2.cjs.map +1 -0
  4. package/dist/KubbDriver-Cq1isv2P.js +2110 -0
  5. package/dist/KubbDriver-Cq1isv2P.js.map +1 -0
  6. package/dist/{types-CC09VtBt.d.ts → createKubb-CYrw_xaR.d.ts} +1414 -1255
  7. package/dist/index.cjs +221 -1074
  8. package/dist/index.cjs.map +1 -1
  9. package/dist/index.d.ts +2 -185
  10. package/dist/index.js +211 -1068
  11. package/dist/index.js.map +1 -1
  12. package/dist/mocks.cjs +30 -21
  13. package/dist/mocks.cjs.map +1 -1
  14. package/dist/mocks.d.ts +5 -5
  15. package/dist/mocks.js +29 -20
  16. package/dist/mocks.js.map +1 -1
  17. package/package.json +6 -18
  18. package/src/FileManager.ts +75 -58
  19. package/src/FileProcessor.ts +48 -38
  20. package/src/KubbDriver.ts +915 -0
  21. package/src/constants.ts +11 -6
  22. package/src/createAdapter.ts +84 -1
  23. package/src/createKubb.ts +1022 -485
  24. package/src/createRenderer.ts +33 -22
  25. package/src/defineGenerator.ts +96 -7
  26. package/src/defineLogger.ts +42 -3
  27. package/src/defineMiddleware.ts +1 -1
  28. package/src/defineParser.ts +1 -1
  29. package/src/definePlugin.ts +304 -8
  30. package/src/defineResolver.ts +271 -150
  31. package/src/devtools.ts +8 -1
  32. package/src/index.ts +2 -2
  33. package/src/mocks.ts +11 -14
  34. package/src/storages/fsStorage.ts +13 -37
  35. package/src/types.ts +39 -1292
  36. package/dist/PluginDriver-BXibeQk-.cjs +0 -1036
  37. package/dist/PluginDriver-BXibeQk-.cjs.map +0 -1
  38. package/dist/PluginDriver-DV3p2Hky.js +0 -945
  39. package/dist/PluginDriver-DV3p2Hky.js.map +0 -1
  40. package/src/Kubb.ts +0 -300
  41. package/src/PluginDriver.ts +0 -424
  42. package/src/renderNode.ts +0 -35
  43. package/src/utils/diagnostics.ts +0 -18
  44. package/src/utils/isInputPath.ts +0 -10
  45. package/src/utils/packageJSON.ts +0 -99
@@ -3,53 +3,64 @@ import type { FileNode } from '@kubb/ast'
3
3
  /**
4
4
  * Minimal interface any Kubb renderer must satisfy.
5
5
  *
6
- * The generic `TElement` is the type of the element the renderer accepts
7
- * e.g. `KubbReactElement` for `@kubb/renderer-jsx`, or a custom type for
8
- * your own renderer. Defaults to `unknown` so that generators which do not
9
- * care about the element type continue to work without specifying it.
10
- *
11
- * This allows core to drive rendering without a hard dependency on
12
- * `@kubb/renderer-jsx` or any specific renderer implementation.
6
+ * `TElement` is the type the renderer accepts, for example `KubbReactElement`
7
+ * for `@kubb/renderer-jsx` or a custom type for your own renderer. Defaults to
8
+ * `unknown` so generators that don't care about the element type work without
9
+ * specifying it.
13
10
  */
14
11
  export type Renderer<TElement = unknown> = {
12
+ /**
13
+ * Renders `element` and populates {@link files} with the resulting {@link FileNode} objects.
14
+ * Called once per render cycle; must resolve before {@link files} is read.
15
+ */
15
16
  render(element: TElement): Promise<void>
17
+ /**
18
+ * Tears down the renderer and releases any held resources.
19
+ * Pass an `Error` to signal a failure, a number for an exit code, or omit for a clean shutdown.
20
+ */
16
21
  unmount(error?: Error | number | null): void
22
+ /**
23
+ * Releases any held resources. `[Symbol.dispose]` delegates here.
24
+ */
25
+ dispose(): void
26
+ /**
27
+ * Accumulated {@link FileNode} results produced by the last {@link render} call.
28
+ * Not populated when {@link stream} is implemented.
29
+ */
17
30
  readonly files: Array<FileNode>
31
+ /**
32
+ * When present, core calls this instead of {@link render} and {@link files},
33
+ * forwarding each file to `FileManager` as soon as it is ready.
34
+ */
35
+ stream?(element: TElement): Iterable<FileNode>
36
+ /**
37
+ * Disposer hook so renderers participate in `using` blocks: `using r = rendererFactory()`
38
+ * guarantees {@link dispose} runs on every exit path, including thrown errors.
39
+ */
40
+ [Symbol.dispose](): void
18
41
  }
19
42
 
20
43
  /**
21
- * A factory function that produces a fresh {@link Renderer} per render.
44
+ * A factory function that produces a fresh {@link Renderer} per render cycle.
22
45
  *
23
46
  * Generators use this to declare which renderer handles their output.
24
47
  */
25
48
  export type RendererFactory<TElement = unknown> = () => Renderer<TElement>
26
49
 
27
50
  /**
28
- * Creates a renderer factory for use in generator definitions.
29
- *
30
- * Wrap your renderer factory function with this helper to register it as the
31
- * renderer for a generator. Core will call this factory once per render cycle
32
- * to obtain a fresh renderer instance.
51
+ * Wraps a renderer factory for use in generator definitions.
33
52
  *
34
53
  * @example
35
54
  * ```ts
36
- * // packages/renderer-jsx/src/index.ts
37
55
  * export const jsxRenderer = createRenderer(() => {
38
56
  * const runtime = new Runtime()
39
57
  * return {
40
58
  * async render(element) { await runtime.render(element) },
41
59
  * get files() { return runtime.nodes },
60
+ * dispose() { runtime.unmount() },
42
61
  * unmount(error) { runtime.unmount(error) },
43
62
  * }
44
63
  * })
45
- *
46
- * // packages/plugin-zod/src/generators/zodGenerator.tsx
47
- * import { jsxRenderer } from '@kubb/renderer-jsx'
48
- * export const zodGenerator = defineGenerator<PluginZod>({
49
- * name: 'zod',
50
- * renderer: jsxRenderer,
51
- * schema(node, options) { return <File ...>...</File> },
52
- * })
53
64
  * ```
54
65
  */
55
66
  export function createRenderer<TElement = unknown>(factory: RendererFactory<TElement>): RendererFactory<TElement> {
@@ -1,9 +1,98 @@
1
- import type { PossiblePromise } from '@internals/utils'
2
- import type { FileNode, OperationNode, SchemaNode } from '@kubb/ast'
1
+ import type { AsyncEventEmitter, PossiblePromise } from '@internals/utils'
2
+ import type { FileNode, InputMeta, OperationNode, SchemaNode, Visitor } from '@kubb/ast'
3
+ import type { Adapter } from './createAdapter.ts'
3
4
  import type { RendererFactory } from './createRenderer.ts'
4
- import type { GeneratorContext, PluginFactoryOptions } from './types.ts'
5
+ import type { KubbHooks } from './types.ts'
6
+ import type { KubbDriver } from './KubbDriver.ts'
7
+ import type { Plugin, PluginFactoryOptions } from './definePlugin.ts'
8
+ import type { Resolver } from './defineResolver.ts'
9
+ import type { Config, DevtoolsOptions } from './types.ts'
5
10
 
6
- export type { GeneratorContext } from './types.ts'
11
+ /**
12
+ * Context object passed to generator `schema`, `operation`, and `operations` methods.
13
+ *
14
+ * The adapter is always defined (guaranteed by `runPluginAstHooks`) so no runtime checks
15
+ * are needed. `ctx.options` carries resolved per-node options after exclude/include/override
16
+ * filtering for individual schema/operation calls, or plugin-level options for operations.
17
+ */
18
+ export type GeneratorContext<TOptions extends PluginFactoryOptions = PluginFactoryOptions> = {
19
+ config: Config
20
+ /**
21
+ * Absolute path to the current plugin's output directory.
22
+ */
23
+ root: string
24
+ /**
25
+ * Determine output mode based on the output config.
26
+ * Returns `'single'` when `output.path` is a file, `'split'` for a directory.
27
+ */
28
+ getMode: (output: { path: string }) => 'single' | 'split'
29
+ driver: KubbDriver
30
+ /**
31
+ * Get a plugin by name, typed via `Kubb.PluginRegistry` when registered.
32
+ */
33
+ getPlugin<TName extends keyof Kubb.PluginRegistry>(name: TName): Plugin<Kubb.PluginRegistry[TName]> | undefined
34
+ getPlugin(name: string): Plugin | undefined
35
+ /**
36
+ * Get a plugin by name, throws an error if not found.
37
+ */
38
+ requirePlugin<TName extends keyof Kubb.PluginRegistry>(name: TName): Plugin<Kubb.PluginRegistry[TName]>
39
+ requirePlugin(name: string): Plugin
40
+ /**
41
+ * Get a resolver by plugin name, typed via `Kubb.PluginRegistry` when registered.
42
+ */
43
+ getResolver<TName extends keyof Kubb.PluginRegistry>(name: TName): Kubb.PluginRegistry[TName]['resolver']
44
+ getResolver(name: string): Resolver
45
+ /**
46
+ * Add files only if they don't exist.
47
+ */
48
+ addFile: (...file: Array<FileNode>) => Promise<void>
49
+ /**
50
+ * Merge sources into the same output file.
51
+ */
52
+ upsertFile: (...file: Array<FileNode>) => Promise<void>
53
+ hooks: AsyncEventEmitter<KubbHooks>
54
+ /**
55
+ * The current plugin instance.
56
+ */
57
+ plugin: Plugin<TOptions>
58
+ /**
59
+ * The current plugin's resolver.
60
+ */
61
+ resolver: TOptions['resolver']
62
+ /**
63
+ * The current plugin's transformer.
64
+ */
65
+ transformer: Visitor | undefined
66
+ /**
67
+ * Emit a warning.
68
+ */
69
+ warn: (message: string) => void
70
+ /**
71
+ * Emit an error.
72
+ */
73
+ error: (error: string | Error) => void
74
+ /**
75
+ * Emit an info message.
76
+ */
77
+ info: (message: string) => void
78
+ /**
79
+ * Open the current input node in Kubb Studio.
80
+ */
81
+ openInStudio: (options?: DevtoolsOptions) => Promise<void>
82
+ /**
83
+ * The configured adapter instance.
84
+ */
85
+ adapter: Adapter
86
+ /**
87
+ * Document metadata from the adapter — title, version, base URL, and pre-computed
88
+ * schema index fields (`circularNames`, `enumNames`).
89
+ */
90
+ meta: InputMeta
91
+ /**
92
+ * Resolved options after exclude/include/override filtering.
93
+ */
94
+ options: TOptions['resolvedOptions']
95
+ }
7
96
 
8
97
  /**
9
98
  * Declares a named generator unit that walks the AST and emits files.
@@ -57,19 +146,19 @@ export type Generator<TOptions extends PluginFactoryOptions = PluginFactoryOptio
57
146
  renderer?: RendererFactory<TElement> | null
58
147
  /**
59
148
  * Called for each schema node in the AST walk.
60
- * `ctx` carries the plugin context with `adapter` and `inputNode` guaranteed present,
149
+ * `ctx` carries the plugin context with `adapter` and `meta` (document metadata),
61
150
  * plus `ctx.options` with the per-node resolved options (after exclude/include/override).
62
151
  */
63
152
  schema?: (node: SchemaNode, ctx: GeneratorContext<TOptions>) => PossiblePromise<TElement | Array<FileNode> | void>
64
153
  /**
65
154
  * Called for each operation node in the AST walk.
66
- * `ctx` carries the plugin context with `adapter` and `inputNode` guaranteed present,
155
+ * `ctx` carries the plugin context with `adapter` and `meta` (document metadata),
67
156
  * plus `ctx.options` with the per-node resolved options (after exclude/include/override).
68
157
  */
69
158
  operation?: (node: OperationNode, ctx: GeneratorContext<TOptions>) => PossiblePromise<TElement | Array<FileNode> | void>
70
159
  /**
71
160
  * Called once after all operations have been walked.
72
- * `ctx` carries the plugin context with `adapter` and `inputNode` guaranteed present,
161
+ * `ctx` carries the plugin context with `adapter` and `meta` (document metadata),
73
162
  * plus `ctx.options` with the plugin-level options for the batch call.
74
163
  */
75
164
  operations?: (nodes: Array<OperationNode>, ctx: GeneratorContext<TOptions>) => PossiblePromise<TElement | Array<FileNode> | void>
@@ -1,9 +1,35 @@
1
- import type { Logger, LoggerOptions, UserLogger } from './types.ts'
1
+ import type { AsyncEventEmitter } from '@internals/utils'
2
+ import type { logLevel } from './constants.ts'
3
+ import type { KubbHooks } from './types.ts'
4
+
5
+ export type LoggerOptions = {
6
+ /**
7
+ * Log level for output verbosity.
8
+ * @default 3
9
+ */
10
+ logLevel: (typeof logLevel)[keyof typeof logLevel]
11
+ }
12
+
13
+ /**
14
+ * Shared context passed to plugins, parsers, and other internals.
15
+ */
16
+ export type LoggerContext = AsyncEventEmitter<KubbHooks>
17
+
18
+ export type Logger<TOptions extends LoggerOptions = LoggerOptions, TInstallReturn = void> = {
19
+ name: string
20
+ install: (context: LoggerContext, options?: TOptions) => TInstallReturn | Promise<TInstallReturn>
21
+ }
22
+
23
+ export type UserLogger<TOptions extends LoggerOptions = LoggerOptions, TInstallReturn = void> = Logger<TOptions, TInstallReturn>
2
24
 
3
25
  /**
4
26
  * Wraps a logger definition into a typed {@link Logger}.
5
27
  *
6
- * @example
28
+ * The optional second type parameter `TInstallReturn` allows loggers to return
29
+ * a value from `install` — for example, a sink factory that the caller can
30
+ * forward to hook execution.
31
+ *
32
+ * @example Basic logger
7
33
  * ```ts
8
34
  * export const myLogger = defineLogger({
9
35
  * name: 'my-logger',
@@ -13,7 +39,20 @@ import type { Logger, LoggerOptions, UserLogger } from './types.ts'
13
39
  * },
14
40
  * })
15
41
  * ```
42
+ *
43
+ * @example Logger that returns a hook sink factory
44
+ * ```ts
45
+ * export const myLogger = defineLogger<LoggerOptions, HookSinkFactory>({
46
+ * name: 'my-logger',
47
+ * install(context, options) {
48
+ * // … register event handlers …
49
+ * return (commandWithArgs) => ({ onStdout: console.log })
50
+ * },
51
+ * })
52
+ * ```
16
53
  */
17
- export function defineLogger<Options extends LoggerOptions = LoggerOptions>(logger: UserLogger<Options>): Logger<Options> {
54
+ export function defineLogger<Options extends LoggerOptions = LoggerOptions, TInstallReturn = void>(
55
+ logger: UserLogger<Options, TInstallReturn>,
56
+ ): Logger<Options, TInstallReturn> {
18
57
  return logger
19
58
  }
@@ -1,4 +1,4 @@
1
- import type { KubbHooks } from './Kubb.ts'
1
+ import type { KubbHooks } from './types.ts'
2
2
 
3
3
  /**
4
4
  * A middleware instance produced by calling a factory created with `defineMiddleware`.
@@ -17,7 +17,7 @@ export type Parser<TMeta extends object = any> = {
17
17
  /**
18
18
  * Convert a resolved file to a string.
19
19
  */
20
- parse(file: FileNode<TMeta>, options?: PrintOptions): Promise<string> | string
20
+ parse(file: FileNode<TMeta>, options?: PrintOptions): string
21
21
  }
22
22
 
23
23
  /**
@@ -1,5 +1,260 @@
1
- import type { KubbHooks } from './Kubb.ts'
2
- import type { KubbPluginSetupContext, PluginFactoryOptions } from './types.ts'
1
+ import { extname } from 'node:path'
2
+ import type { FileNode, HttpMethod, InputMeta, UserFileNode, Visitor } from '@kubb/ast'
3
+ import type { RendererFactory } from './createRenderer.ts'
4
+ import type { Generator } from './defineGenerator.ts'
5
+ import type { Resolver } from './defineResolver.ts'
6
+ import type { Config, KubbHooks } from './types.ts'
7
+
8
+ /**
9
+ * Safely extracts a type from a registry, returning `{}` if the key doesn't exist.
10
+ * Enables optional interface augmentation for `Kubb.ConfigOptionsRegistry` and `Kubb.PluginOptionsRegistry`
11
+ * without requiring changes to core.
12
+ *
13
+ * @internal
14
+ */
15
+ type ExtractRegistryKey<T, K extends PropertyKey> = K extends keyof T ? T[K] : {}
16
+
17
+ /**
18
+ * Output configuration for generated files.
19
+ */
20
+ export type Output<_TOptions = unknown> = {
21
+ /**
22
+ * Output folder or file path for generated code.
23
+ */
24
+ path: string
25
+ /**
26
+ * Text or function prepended to every generated file.
27
+ * When a function, receives the document metadata and returns a string.
28
+ */
29
+ banner?: string | ((meta?: InputMeta) => string)
30
+ /**
31
+ * Text or function appended to every generated file.
32
+ * When a function, receives the document metadata and returns a string.
33
+ */
34
+ footer?: string | ((meta?: InputMeta) => string)
35
+ /**
36
+ * Whether to override existing external files if they already exist.
37
+ * @default false
38
+ */
39
+ override?: boolean
40
+ } & ExtractRegistryKey<Kubb.PluginOptionsRegistry, 'output'>
41
+
42
+ export type Group = {
43
+ /**
44
+ * How to group files into subdirectories.
45
+ * - `'tag'` — group by OpenAPI tags
46
+ * - `'path'` — group by OpenAPI paths
47
+ */
48
+ type: 'tag' | 'path'
49
+ /**
50
+ * Function that returns the subdirectory name for a group value.
51
+ * Defaults to `${camelCase(group)}Controller` for tags, first path segment for paths.
52
+ */
53
+ name?: (context: { group: string }) => string
54
+ }
55
+
56
+ type ByTag = {
57
+ /**
58
+ * Filter by OpenAPI `tags` field. Matches one or more tags assigned to operations.
59
+ */
60
+ type: 'tag'
61
+ /**
62
+ * Tag name to match (case-sensitive). Can be a literal string or regex pattern.
63
+ */
64
+ pattern: string | RegExp
65
+ }
66
+
67
+ type ByOperationId = {
68
+ /**
69
+ * Filter by OpenAPI `operationId` field. Each operation (GET, POST, etc.) has a unique identifier.
70
+ */
71
+ type: 'operationId'
72
+ /**
73
+ * Operation ID to match (case-sensitive). Can be a literal string or regex pattern.
74
+ */
75
+ pattern: string | RegExp
76
+ }
77
+
78
+ type ByPath = {
79
+ /**
80
+ * Filter by OpenAPI `path` (URL endpoint). Useful to group or filter by service segments like `/pets`, `/users`, etc.
81
+ */
82
+ type: 'path'
83
+ /**
84
+ * URL path to match (case-sensitive). Can be a literal string or regex pattern. Matches against the full path.
85
+ */
86
+ pattern: string | RegExp
87
+ }
88
+
89
+ type ByMethod = {
90
+ /**
91
+ * Filter by HTTP method: `'get'`, `'post'`, `'put'`, `'delete'`, `'patch'`, `'head'`, `'options'`.
92
+ */
93
+ type: 'method'
94
+ /**
95
+ * HTTP method to match (case-insensitive when using string, or regex for dynamic matching).
96
+ */
97
+ pattern: HttpMethod | RegExp
98
+ }
99
+ // TODO implement as alternative for ByMethod
100
+ // type ByMethods = {
101
+ // type: 'methods'
102
+ // pattern: Array<HttpMethod>
103
+ // }
104
+
105
+ type BySchemaName = {
106
+ /**
107
+ * Filter by schema component name (TypeScript or JSON schema). Matches schemas in `#/components/schemas`.
108
+ */
109
+ type: 'schemaName'
110
+ /**
111
+ * Schema name to match (case-sensitive). Can be a literal string or regex pattern.
112
+ */
113
+ pattern: string | RegExp
114
+ }
115
+
116
+ type ByContentType = {
117
+ /**
118
+ * Filter by response or request content type: `'application/json'`, `'application/xml'`, etc.
119
+ */
120
+ type: 'contentType'
121
+ /**
122
+ * Content type to match (case-sensitive). Can be a literal string or regex pattern.
123
+ */
124
+ pattern: string | RegExp
125
+ }
126
+
127
+ /**
128
+ * A pattern filter that prevents matching nodes from being generated.
129
+ *
130
+ * Use to skip code generation for specific operations or schemas. For example, exclude deprecated endpoints
131
+ * or internal-only schemas. Can filter by tag, operationId, path, HTTP method, content type, or schema name.
132
+ *
133
+ * @example
134
+ * ```ts
135
+ * exclude: [
136
+ * { type: 'tag', pattern: 'internal' }, // skip "internal" tag
137
+ * { type: 'path', pattern: /^\/admin/ }, // skip all /admin endpoints
138
+ * { type: 'operationId', pattern: 'deprecated_*' } // skip operationIds matching pattern
139
+ * ]
140
+ * ```
141
+ */
142
+ export type Exclude = ByTag | ByOperationId | ByPath | ByMethod | ByContentType | BySchemaName
143
+
144
+ /**
145
+ * A pattern filter that restricts generation to only matching nodes.
146
+ *
147
+ * Use to generate code for a subset of operations or schemas. For example, only generate for a specific service
148
+ * tag or only for "production" endpoints. Can filter by tag, operationId, path, HTTP method, content type, or schema name.
149
+ *
150
+ * @example
151
+ * ```ts
152
+ * include: [
153
+ * { type: 'tag', pattern: 'public' }, // generate only "public" tag
154
+ * { type: 'path', pattern: /^\/api\/v1/ }, // generate only v1 endpoints
155
+ * ]
156
+ * ```
157
+ */
158
+ export type Include = ByTag | ByOperationId | ByPath | ByMethod | ByContentType | BySchemaName
159
+
160
+ /**
161
+ * A pattern filter paired with partial option overrides applied when the pattern matches.
162
+ *
163
+ * Use to customize generation for specific operations or schemas. For example, apply different output paths
164
+ * for different tags, or use custom resolver functions per operation. Can filter by tag, operationId, path,
165
+ * HTTP method, schema name, or content type.
166
+ *
167
+ * @example
168
+ * ```ts
169
+ * override: [
170
+ * {
171
+ * type: 'tag',
172
+ * pattern: 'admin',
173
+ * options: { output: { path: './src/gen/admin' } } // admin APIs go to separate folder
174
+ * },
175
+ * {
176
+ * type: 'operationId',
177
+ * pattern: 'listPets',
178
+ * options: { exclude: true } // skip this specific operation
179
+ * }
180
+ * ]
181
+ * ```
182
+ */
183
+ export type Override<TOptions> = (ByTag | ByOperationId | ByPath | ByMethod | BySchemaName | ByContentType) & {
184
+ //TODO should be options: Omit<Partial<TOptions>, 'override'>
185
+ options: Partial<TOptions>
186
+ }
187
+
188
+ export type PluginFactoryOptions<
189
+ /**
190
+ * Unique plugin name.
191
+ */
192
+ TName extends string = string,
193
+ /**
194
+ * User-facing plugin options.
195
+ */
196
+ TOptions extends object = object,
197
+ /**
198
+ * Plugin options after defaults are applied.
199
+ */
200
+ TResolvedOptions extends object = TOptions,
201
+ /**
202
+ * Resolver that encapsulates naming and path-resolution helpers.
203
+ * Define with `defineResolver` and export alongside the plugin.
204
+ */
205
+ TResolver extends Resolver = Resolver,
206
+ > = {
207
+ name: TName
208
+ options: TOptions
209
+ resolvedOptions: TResolvedOptions
210
+ resolver: TResolver
211
+ }
212
+
213
+ /**
214
+ * Context for hook-style plugin `kubb:plugin:setup` handler.
215
+ * Provides methods to register generators, configure resolvers, transformers, and renderers.
216
+ */
217
+ export type KubbPluginSetupContext<TFactory extends PluginFactoryOptions = PluginFactoryOptions> = {
218
+ /**
219
+ * Register a generator dynamically. Generators fire during the AST walk (schema/operation/operations)
220
+ * just like generators declared statically on `createPlugin`.
221
+ */
222
+ addGenerator<TElement = unknown>(generator: Generator<TFactory, TElement>): void
223
+ /**
224
+ * Set or override the resolver for this plugin.
225
+ * The resolver controls file naming and path resolution.
226
+ */
227
+ setResolver(resolver: Partial<TFactory['resolver']>): void
228
+ /**
229
+ * Set the AST transformer to pre-process nodes before they reach generators.
230
+ */
231
+ setTransformer(visitor: Visitor): void
232
+ /**
233
+ * Set the renderer factory to process JSX elements from generators.
234
+ */
235
+ setRenderer(renderer: RendererFactory): void
236
+ /**
237
+ * Set resolved options merged into the normalized plugin's `options`.
238
+ * Call this in `kubb:plugin:setup` to provide options generators need.
239
+ */
240
+ setOptions(options: TFactory['resolvedOptions']): void
241
+ /**
242
+ * Inject a raw file into the build output, bypassing the generation pipeline.
243
+ */
244
+ injectFile(userFileNode: UserFileNode): void
245
+ /**
246
+ * Merge a partial config update into the current build configuration.
247
+ */
248
+ updateConfig(config: Partial<Config>): void
249
+ /**
250
+ * The resolved build configuration at setup time.
251
+ */
252
+ config: Config
253
+ /**
254
+ * The plugin's user-provided options.
255
+ */
256
+ options: TFactory['options']
257
+ }
3
258
 
4
259
  /**
5
260
  * A plugin object produced by `definePlugin`.
@@ -37,20 +292,52 @@ export type Plugin<TFactory extends PluginFactoryOptions = PluginFactoryOptions>
37
292
  * Any event from the global `KubbHooks` map can be subscribed to here.
38
293
  */
39
294
  hooks: {
40
- [K in Exclude<keyof KubbHooks, 'kubb:plugin:setup'>]?: (...args: KubbHooks[K]) => void | Promise<void>
295
+ [K in keyof KubbHooks as K extends 'kubb:plugin:setup' ? never : K]?: (...args: KubbHooks[K]) => void | Promise<void>
41
296
  } & {
42
297
  'kubb:plugin:setup'?(ctx: KubbPluginSetupContext<TFactory>): void | Promise<void>
43
298
  }
44
299
  }
45
300
 
46
301
  /**
47
- * Returns `true` when `plugin` is a hook-style plugin created with `definePlugin`.
302
+ * Normalized plugin after setup, with runtime fields populated.
303
+ * For internal use only — plugins use the public `Plugin` type externally.
48
304
  *
49
- * Used by `PluginDriver` to distinguish hook-style plugins from legacy `createPlugin` plugins
50
- * so it can normalize them and register their handlers on the `AsyncEventEmitter`.
305
+ * @internal
51
306
  */
52
- export function isPlugin(plugin: unknown): plugin is Plugin {
53
- return typeof plugin === 'object' && plugin !== null && 'hooks' in plugin
307
+ export type NormalizedPlugin<TOptions extends PluginFactoryOptions = PluginFactoryOptions> = Plugin<TOptions> & {
308
+ options: TOptions['resolvedOptions'] & {
309
+ output: Output
310
+ include?: Array<Include>
311
+ exclude: Array<Exclude>
312
+ override: Array<Override<TOptions['resolvedOptions']>>
313
+ }
314
+ resolver: TOptions['resolver']
315
+ transformer?: Visitor
316
+ renderer?: RendererFactory
317
+ generators?: Array<Generator>
318
+ apply?: (config: Config) => boolean
319
+ version?: string
320
+ }
321
+
322
+ export type KubbPluginStartContext = {
323
+ plugin: NormalizedPlugin
324
+ }
325
+
326
+ export type KubbPluginEndContext = {
327
+ plugin: NormalizedPlugin
328
+ duration: number
329
+ success: boolean
330
+ error?: Error
331
+ config: Config
332
+ /**
333
+ * Returns all files currently in the file manager (lazy snapshot).
334
+ * Includes files added by plugins that have already run.
335
+ */
336
+ readonly files: ReadonlyArray<FileNode>
337
+ /**
338
+ * Upsert one or more files into the file manager.
339
+ */
340
+ upsertFile: (...files: Array<FileNode>) => void
54
341
  }
55
342
 
56
343
  /**
@@ -81,3 +368,12 @@ export function definePlugin<TFactory extends PluginFactoryOptions = PluginFacto
81
368
  ): (options?: TFactory['options']) => Plugin<TFactory> {
82
369
  return (options) => factory(options ?? ({} as TFactory['options']))
83
370
  }
371
+
372
+ /**
373
+ * Returns `'single'` when `fileOrFolder` has a file extension, `'split'` otherwise.
374
+ * Used to determine whether an output path targets a single file or a directory.
375
+ */
376
+ export function getMode(fileOrFolder: string | undefined | null): 'single' | 'split' {
377
+ if (!fileOrFolder) return 'split'
378
+ return extname(fileOrFolder) ? 'single' : 'split'
379
+ }