@kubb/core 5.0.0-alpha.3 → 5.0.0-alpha.31

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 (60) hide show
  1. package/dist/PluginDriver-D0dY_hpJ.d.ts +1986 -0
  2. package/dist/{chunk-ByKO4r7w.cjs → chunk-MlS0t1Af.cjs} +15 -0
  3. package/dist/chunk-O_arW02_.js +17 -0
  4. package/dist/hooks.cjs +13 -28
  5. package/dist/hooks.cjs.map +1 -1
  6. package/dist/hooks.d.ts +11 -37
  7. package/dist/hooks.js +14 -28
  8. package/dist/hooks.js.map +1 -1
  9. package/dist/index.cjs +1469 -831
  10. package/dist/index.cjs.map +1 -1
  11. package/dist/index.d.ts +572 -191
  12. package/dist/index.js +1443 -826
  13. package/dist/index.js.map +1 -1
  14. package/package.json +7 -7
  15. package/src/Kubb.ts +38 -56
  16. package/src/KubbFile.ts +143 -0
  17. package/src/{PluginManager.ts → PluginDriver.ts} +159 -170
  18. package/src/build.ts +213 -65
  19. package/src/constants.ts +39 -6
  20. package/src/createAdapter.ts +25 -0
  21. package/src/createPlugin.ts +30 -0
  22. package/src/createStorage.ts +58 -0
  23. package/src/{config.ts → defineConfig.ts} +11 -16
  24. package/src/defineGenerator.ts +126 -0
  25. package/src/defineLogger.ts +13 -3
  26. package/src/defineParser.ts +57 -0
  27. package/src/definePresets.ts +16 -0
  28. package/src/defineResolver.ts +454 -0
  29. package/src/hooks/index.ts +1 -6
  30. package/src/hooks/useDriver.ts +11 -0
  31. package/src/hooks/useMode.ts +4 -4
  32. package/src/hooks/usePlugin.ts +3 -3
  33. package/src/index.ts +22 -10
  34. package/src/renderNode.tsx +25 -0
  35. package/src/storages/fsStorage.ts +2 -2
  36. package/src/storages/memoryStorage.ts +2 -2
  37. package/src/types.ts +639 -52
  38. package/src/utils/FunctionParams.ts +2 -2
  39. package/src/utils/TreeNode.ts +40 -2
  40. package/src/utils/diagnostics.ts +4 -1
  41. package/src/utils/executeStrategies.ts +29 -10
  42. package/src/utils/formatters.ts +10 -21
  43. package/src/utils/getBarrelFiles.ts +80 -10
  44. package/src/utils/getConfigs.ts +9 -23
  45. package/src/utils/getPreset.ts +78 -0
  46. package/src/utils/isInputPath.ts +8 -0
  47. package/src/utils/linters.ts +23 -3
  48. package/src/utils/packageJSON.ts +76 -0
  49. package/dist/chunk--u3MIqq1.js +0 -8
  50. package/dist/types-CiPWLv-5.d.ts +0 -1001
  51. package/src/BarrelManager.ts +0 -74
  52. package/src/PackageManager.ts +0 -180
  53. package/src/PromiseManager.ts +0 -40
  54. package/src/defineAdapter.ts +0 -22
  55. package/src/definePlugin.ts +0 -12
  56. package/src/defineStorage.ts +0 -56
  57. package/src/errors.ts +0 -1
  58. package/src/hooks/useKubb.ts +0 -22
  59. package/src/hooks/usePluginManager.ts +0 -11
  60. package/src/utils/getPlugins.ts +0 -23
@@ -1,14 +1,13 @@
1
1
  import { basename, extname, resolve } from 'node:path'
2
2
  import { performance } from 'node:perf_hooks'
3
3
  import type { AsyncEventEmitter } from '@internals/utils'
4
- import { setUniqueName, transformReservedWord } from '@internals/utils'
4
+ import { isPromiseRejectedResult, transformReservedWord } from '@internals/utils'
5
5
  import type { RootNode } from '@kubb/ast/types'
6
- import type { KubbFile } from '@kubb/fabric-core/types'
7
- import type { Fabric } from '@kubb/react-fabric'
8
- import { CORE_PLUGIN_NAME, DEFAULT_STUDIO_URL } from './constants.ts'
6
+ import type { Fabric as FabricType } from '@kubb/fabric-core/types'
7
+ import { DEFAULT_STUDIO_URL } from './constants.ts'
9
8
  import { openInStudio as openInStudioFn } from './devtools.ts'
10
- import { ValidationPluginError } from './errors.ts'
11
- import { isPromiseRejectedResult, PromiseManager } from './PromiseManager.ts'
9
+ import type * as KubbFile from './KubbFile.ts'
10
+
12
11
  import type {
13
12
  Adapter,
14
13
  Config,
@@ -23,11 +22,19 @@ import type {
23
22
  PluginWithLifeCycle,
24
23
  ResolveNameParams,
25
24
  ResolvePathParams,
26
- UserPlugin,
27
25
  } from './types.ts'
26
+ import { hookFirst, hookParallel, hookSeq } from './utils/executeStrategies.ts'
28
27
 
29
28
  type RequiredPluginLifecycle = Required<PluginLifecycle>
30
29
 
30
+ /**
31
+ * Hook dispatch strategy used by the `PluginDriver`.
32
+ *
33
+ * - `hookFirst` — stops at the first non-null result.
34
+ * - `hookForPlugin` — calls only the matching plugin.
35
+ * - `hookParallel` — calls all plugins concurrently.
36
+ * - `hookSeq` — calls all plugins in order, threading the result.
37
+ */
31
38
  export type Strategy = 'hookFirst' | 'hookForPlugin' | 'hookParallel' | 'hookSeq'
32
39
 
33
40
  type ParseResult<H extends PluginLifecycleHooks> = RequiredPluginLifecycle[H]
@@ -40,7 +47,7 @@ type SafeParseResult<H extends PluginLifecycleHooks, Result = ReturnType<ParseRe
40
47
  // inspired by: https://github.com/rollup/rollup/blob/master/src/utils/PluginDriver.ts#
41
48
 
42
49
  type Options = {
43
- fabric: Fabric
50
+ fabric: FabricType
44
51
  events: AsyncEventEmitter<KubbEvents>
45
52
  /**
46
53
  * @default Number.POSITIVE_INFINITY
@@ -48,7 +55,10 @@ type Options = {
48
55
  concurrency?: number
49
56
  }
50
57
 
51
- type GetFileProps<TOptions = object> = {
58
+ /**
59
+ * Parameters accepted by `PluginDriver.getFile` to resolve a generated file descriptor.
60
+ */
61
+ export type GetFileOptions<TOptions = object> = {
52
62
  name: string
53
63
  mode?: KubbFile.Mode
54
64
  extname: KubbFile.Extname
@@ -56,6 +66,15 @@ type GetFileProps<TOptions = object> = {
56
66
  options?: TOptions
57
67
  }
58
68
 
69
+ /**
70
+ * Returns `'single'` when `fileOrFolder` has a file extension, `'split'` otherwise.
71
+ *
72
+ * @example
73
+ * ```ts
74
+ * getMode('src/gen/types.ts') // 'single'
75
+ * getMode('src/gen/types') // 'split'
76
+ * ```
77
+ */
59
78
  export function getMode(fileOrFolder: string | undefined | null): KubbFile.Mode {
60
79
  if (!fileOrFolder) {
61
80
  return 'split'
@@ -63,7 +82,9 @@ export function getMode(fileOrFolder: string | undefined | null): KubbFile.Mode
63
82
  return extname(fileOrFolder) ? 'single' : 'split'
64
83
  }
65
84
 
66
- export class PluginManager {
85
+ const hookFirstNullCheck = (state: unknown) => !!(state as SafeParseResult<'resolveName'> | null)?.result
86
+
87
+ export class PluginDriver {
67
88
  readonly config: Config
68
89
  readonly options: Options
69
90
 
@@ -75,21 +96,27 @@ export class PluginManager {
75
96
  adapter: Adapter | undefined = undefined
76
97
  #studioIsOpen = false
77
98
 
78
- readonly #plugins = new Set<Plugin>()
79
- readonly #usedPluginNames: Record<string, number> = {}
80
- readonly #promiseManager
99
+ readonly plugins = new Map<string, Plugin>()
81
100
 
82
101
  constructor(config: Config, options: Options) {
83
102
  this.config = config
84
103
  this.options = options
85
- this.#promiseManager = new PromiseManager({
86
- nullCheck: (state: SafeParseResult<'resolveName'> | null) => !!state?.result,
87
- })
88
- ;[...(config.plugins || [])].forEach((plugin) => {
89
- const parsedPlugin = this.#parse(plugin as UserPlugin)
90
-
91
- this.#plugins.add(parsedPlugin)
92
- })
104
+ config.plugins
105
+ .map((plugin) => Object.assign({ buildStart() {}, buildEnd() {} }, plugin) as unknown as Plugin)
106
+ .filter((plugin) => {
107
+ if (typeof plugin.apply === 'function') {
108
+ return plugin.apply(config)
109
+ }
110
+ return true
111
+ })
112
+ .sort((a, b) => {
113
+ if (b.pre?.includes(a.name)) return 1
114
+ if (b.post?.includes(a.name)) return -1
115
+ return 0
116
+ })
117
+ .forEach((plugin) => {
118
+ this.plugins.set(plugin.name, plugin)
119
+ })
93
120
  }
94
121
 
95
122
  get events() {
@@ -97,16 +124,22 @@ export class PluginManager {
97
124
  }
98
125
 
99
126
  getContext<TOptions extends PluginFactoryOptions>(plugin: Plugin<TOptions>): PluginContext<TOptions> & Record<string, unknown> {
100
- const plugins = [...this.#plugins]
101
- const pluginManager = this
127
+ const driver = this
102
128
 
103
129
  const baseContext = {
104
- fabric: this.options.fabric,
105
- config: this.config,
130
+ fabric: driver.options.fabric,
131
+ config: driver.config,
132
+ get root(): string {
133
+ return resolve(driver.config.root, driver.config.output.path)
134
+ },
135
+ getMode(output: { path: string }): KubbFile.Mode {
136
+ return getMode(resolve(driver.config.root, driver.config.output.path, output.path))
137
+ },
138
+ events: driver.options.events,
106
139
  plugin,
107
- events: this.options.events,
108
- pluginManager: this,
109
- mode: getMode(resolve(this.config.root, this.config.output.path)),
140
+ getPlugin: driver.getPlugin.bind(driver),
141
+ requirePlugin: driver.requirePlugin.bind(driver),
142
+ driver: driver,
110
143
  addFile: async (...files: Array<KubbFile.File>) => {
111
144
  await this.options.fabric.addFile(...files)
112
145
  },
@@ -114,39 +147,52 @@ export class PluginManager {
114
147
  await this.options.fabric.upsertFile(...files)
115
148
  },
116
149
  get rootNode(): RootNode | undefined {
117
- return pluginManager.rootNode
150
+ return driver.rootNode
118
151
  },
119
152
  get adapter(): Adapter | undefined {
120
- return pluginManager.adapter
153
+ return driver.adapter
154
+ },
155
+ get resolver() {
156
+ return plugin.resolver
157
+ },
158
+ get transformer() {
159
+ return plugin.transformer
160
+ },
161
+ warn(message: string) {
162
+ driver.events.emit('warn', message)
163
+ },
164
+ error(error: string | Error) {
165
+ driver.events.emit('error', typeof error === 'string' ? new Error(error) : error)
166
+ },
167
+ info(message: string) {
168
+ driver.events.emit('info', message)
121
169
  },
122
170
  openInStudio(options?: DevtoolsOptions) {
123
- if (!pluginManager.config.devtools || pluginManager.#studioIsOpen) {
171
+ if (!driver.config.devtools || driver.#studioIsOpen) {
124
172
  return
125
173
  }
126
174
 
127
- if (typeof pluginManager.config.devtools !== 'object') {
175
+ if (typeof driver.config.devtools !== 'object') {
128
176
  throw new Error('Devtools must be an object')
129
177
  }
130
178
 
131
- if (!pluginManager.rootNode || !pluginManager.adapter) {
179
+ if (!driver.rootNode || !driver.adapter) {
132
180
  throw new Error('adapter is not defined, make sure you have set the parser in kubb.config.ts')
133
181
  }
134
182
 
135
- pluginManager.#studioIsOpen = true
183
+ driver.#studioIsOpen = true
136
184
 
137
- const studioUrl = pluginManager.config.devtools?.studioUrl ?? DEFAULT_STUDIO_URL
185
+ const studioUrl = driver.config.devtools?.studioUrl ?? DEFAULT_STUDIO_URL
138
186
 
139
- return openInStudioFn(pluginManager.rootNode, studioUrl, options)
187
+ return openInStudioFn(driver.rootNode, studioUrl, options)
140
188
  },
141
189
  } as unknown as PluginContext<TOptions>
142
190
 
143
191
  const mergedExtras: Record<string, unknown> = {}
144
- for (const p of plugins) {
192
+
193
+ for (const p of this.plugins.values()) {
145
194
  if (typeof p.inject === 'function') {
146
- const result = (p.inject as (this: PluginContext, context: PluginContext) => unknown).call(
147
- baseContext as unknown as PluginContext,
148
- baseContext as unknown as PluginContext,
149
- )
195
+ const result = (p.inject as (this: PluginContext) => unknown).call(baseContext as unknown as PluginContext)
150
196
  if (result !== null && typeof result === 'object') {
151
197
  Object.assign(mergedExtras, result)
152
198
  }
@@ -158,12 +204,10 @@ export class PluginManager {
158
204
  ...mergedExtras,
159
205
  }
160
206
  }
161
-
162
- get plugins(): Array<Plugin> {
163
- return this.#getSortedPlugins()
164
- }
165
-
166
- getFile<TOptions = object>({ name, mode, extname, pluginName, options }: GetFileProps<TOptions>): KubbFile.File<{ pluginName: string }> {
207
+ /**
208
+ * @deprecated use resolvers context instead
209
+ */
210
+ getFile<TOptions = object>({ name, mode, extname, pluginName, options }: GetFileOptions<TOptions>): KubbFile.File<{ pluginName: string }> {
167
211
  const resolvedName = mode ? (mode === 'single' ? '' : this.resolveName({ name, pluginName, type: 'file' })) : name
168
212
 
169
213
  const path = this.resolvePath({
@@ -189,6 +233,9 @@ export class PluginManager {
189
233
  }
190
234
  }
191
235
 
236
+ /**
237
+ * @deprecated use resolvers context instead
238
+ */
192
239
  resolvePath = <TOptions = object>(params: ResolvePathParams<TOptions>): KubbFile.Path => {
193
240
  const root = resolve(this.config.root, this.config.output.path)
194
241
  const defaultPath = resolve(root, params.baseName)
@@ -210,7 +257,9 @@ export class PluginManager {
210
257
 
211
258
  return firstResult?.result || defaultPath
212
259
  }
213
- //TODO refactor by using the order of plugins and the cache of the fileManager instead of guessing and recreating the name/path
260
+ /**
261
+ * @deprecated use resolvers context instead
262
+ */
214
263
  resolveName = (params: ResolveNameParams): string => {
215
264
  if (params.pluginName) {
216
265
  const names = this.hookForPluginSync({
@@ -219,9 +268,7 @@ export class PluginManager {
219
268
  parameters: [params.name.trim(), params.type],
220
269
  })
221
270
 
222
- const uniqueNames = new Set(names)
223
-
224
- return transformReservedWord([...uniqueNames].at(0) || params.name)
271
+ return transformReservedWord(names?.at(0) ?? params.name)
225
272
  }
226
273
 
227
274
  const name = this.hookFirstSync({
@@ -244,36 +291,32 @@ export class PluginManager {
244
291
  hookName: H
245
292
  parameters: PluginParameter<H>
246
293
  }): Promise<Array<ReturnType<ParseResult<H>> | null>> {
247
- const plugins = this.getPluginsByName(hookName, pluginName)
294
+ const plugin = this.plugins.get(pluginName)
295
+
296
+ if (!plugin) {
297
+ return [null]
298
+ }
248
299
 
249
300
  this.events.emit('plugins:hook:progress:start', {
250
301
  hookName,
251
- plugins,
302
+ plugins: [plugin],
252
303
  })
253
304
 
254
- const items: Array<ReturnType<ParseResult<H>>> = []
255
-
256
- for (const plugin of plugins) {
257
- const result = await this.#execute<H>({
258
- strategy: 'hookFirst',
259
- hookName,
260
- parameters,
261
- plugin,
262
- })
263
-
264
- if (result !== undefined && result !== null) {
265
- items.push(result)
266
- }
267
- }
305
+ const result = await this.#execute<H>({
306
+ strategy: 'hookFirst',
307
+ hookName,
308
+ parameters,
309
+ plugin,
310
+ })
268
311
 
269
312
  this.events.emit('plugins:hook:progress:end', { hookName })
270
313
 
271
- return items
314
+ return [result]
272
315
  }
316
+
273
317
  /**
274
318
  * Run a specific hookName for plugin x.
275
319
  */
276
-
277
320
  hookForPluginSync<H extends PluginLifecycleHooks>({
278
321
  pluginName,
279
322
  hookName,
@@ -283,20 +326,20 @@ export class PluginManager {
283
326
  hookName: H
284
327
  parameters: PluginParameter<H>
285
328
  }): Array<ReturnType<ParseResult<H>>> | null {
286
- const plugins = this.getPluginsByName(hookName, pluginName)
329
+ const plugin = this.plugins.get(pluginName)
287
330
 
288
- const result = plugins
289
- .map((plugin) => {
290
- return this.#executeSync<H>({
291
- strategy: 'hookFirst',
292
- hookName,
293
- parameters,
294
- plugin,
295
- })
296
- })
297
- .filter((x): x is NonNullable<typeof x> => x !== null)
331
+ if (!plugin) {
332
+ return null
333
+ }
298
334
 
299
- return result
335
+ const result = this.#executeSync<H>({
336
+ strategy: 'hookFirst',
337
+ hookName,
338
+ parameters,
339
+ plugin,
340
+ })
341
+
342
+ return result !== null ? [result] : []
300
343
  }
301
344
 
302
345
  /**
@@ -311,9 +354,10 @@ export class PluginManager {
311
354
  parameters: PluginParameter<H>
312
355
  skipped?: ReadonlySet<Plugin> | null
313
356
  }): Promise<SafeParseResult<H>> {
314
- const plugins = this.#getSortedPlugins(hookName).filter((plugin) => {
315
- return skipped ? !skipped.has(plugin) : true
316
- })
357
+ const plugins: Array<Plugin> = []
358
+ for (const plugin of this.plugins.values()) {
359
+ if (hookName in plugin && (skipped ? !skipped.has(plugin) : true)) plugins.push(plugin)
360
+ }
317
361
 
318
362
  this.events.emit('plugins:hook:progress:start', { hookName, plugins })
319
363
 
@@ -333,7 +377,7 @@ export class PluginManager {
333
377
  }
334
378
  })
335
379
 
336
- const result = await this.#promiseManager.run('first', promises)
380
+ const result = await hookFirst(promises, hookFirstNullCheck)
337
381
 
338
382
  this.events.emit('plugins:hook:progress:end', { hookName })
339
383
 
@@ -353,11 +397,11 @@ export class PluginManager {
353
397
  skipped?: ReadonlySet<Plugin> | null
354
398
  }): SafeParseResult<H> | null {
355
399
  let parseResult: SafeParseResult<H> | null = null
356
- const plugins = this.#getSortedPlugins(hookName).filter((plugin) => {
357
- return skipped ? !skipped.has(plugin) : true
358
- })
359
400
 
360
- for (const plugin of plugins) {
401
+ for (const plugin of this.plugins.values()) {
402
+ if (!(hookName in plugin)) continue
403
+ if (skipped?.has(plugin)) continue
404
+
361
405
  parseResult = {
362
406
  result: this.#executeSync<H>({
363
407
  strategy: 'hookFirst',
@@ -368,9 +412,7 @@ export class PluginManager {
368
412
  plugin,
369
413
  } as SafeParseResult<H>
370
414
 
371
- if (parseResult?.result != null) {
372
- break
373
- }
415
+ if (parseResult.result != null) break
374
416
  }
375
417
 
376
418
  return parseResult
@@ -386,7 +428,10 @@ export class PluginManager {
386
428
  hookName: H
387
429
  parameters?: Parameters<RequiredPluginLifecycle[H]> | undefined
388
430
  }): Promise<Awaited<TOutput>[]> {
389
- const plugins = this.#getSortedPlugins(hookName)
431
+ const plugins: Array<Plugin> = []
432
+ for (const plugin of this.plugins.values()) {
433
+ if (hookName in plugin) plugins.push(plugin)
434
+ }
390
435
  this.events.emit('plugins:hook:progress:start', { hookName, plugins })
391
436
 
392
437
  const pluginStartTimes = new Map<Plugin, number>()
@@ -403,13 +448,11 @@ export class PluginManager {
403
448
  }
404
449
  })
405
450
 
406
- const results = await this.#promiseManager.run('parallel', promises, {
407
- concurrency: this.options.concurrency,
408
- })
451
+ const results = await hookParallel(promises, this.options.concurrency)
409
452
 
410
453
  results.forEach((result, index) => {
411
454
  if (isPromiseRejectedResult<Error>(result)) {
412
- const plugin = this.#getSortedPlugins(hookName)[index]
455
+ const plugin = plugins[index]
413
456
 
414
457
  if (plugin) {
415
458
  const startTime = pluginStartTimes.get(plugin) ?? performance.now()
@@ -438,7 +481,10 @@ export class PluginManager {
438
481
  * Chains plugins
439
482
  */
440
483
  async hookSeq<H extends PluginLifecycleHooks>({ hookName, parameters }: { hookName: H; parameters?: PluginParameter<H> }): Promise<void> {
441
- const plugins = this.#getSortedPlugins(hookName)
484
+ const plugins: Array<Plugin> = []
485
+ for (const plugin of this.plugins.values()) {
486
+ if (hookName in plugin) plugins.push(plugin)
487
+ }
442
488
  this.events.emit('plugins:hook:progress:start', { hookName, plugins })
443
489
 
444
490
  const promises = plugins.map((plugin) => {
@@ -451,67 +497,28 @@ export class PluginManager {
451
497
  })
452
498
  })
453
499
 
454
- await this.#promiseManager.run('seq', promises)
500
+ await hookSeq(promises)
455
501
 
456
502
  this.events.emit('plugins:hook:progress:end', { hookName })
457
503
  }
458
504
 
459
- #getSortedPlugins(hookName?: keyof PluginLifecycle): Array<Plugin> {
460
- const plugins = [...this.#plugins]
461
-
462
- if (hookName) {
463
- return plugins.filter((plugin) => hookName in plugin)
464
- }
465
- // TODO add test case for sorting with pre/post
466
-
467
- return plugins
468
- .map((plugin) => {
469
- if (plugin.pre) {
470
- let missingPlugins = plugin.pre.filter((pluginName) => !plugins.find((pluginToFind) => pluginToFind.name === pluginName))
471
-
472
- // when adapter is set, we can ignore the depends on plugin-oas, in v5 this will not be needed anymore
473
- if (missingPlugins.includes('plugin-oas') && this.adapter) {
474
- missingPlugins = missingPlugins.filter((pluginName) => pluginName !== 'plugin-oas')
475
- }
476
-
477
- if (missingPlugins.length > 0) {
478
- throw new ValidationPluginError(`The plugin '${plugin.name}' has a pre set that references missing plugins for '${missingPlugins.join(', ')}'`)
479
- }
480
- }
481
-
482
- return plugin
483
- })
484
- .sort((a, b) => {
485
- if (b.pre?.includes(a.name)) {
486
- return 1
487
- }
488
- if (b.post?.includes(a.name)) {
489
- return -1
490
- }
491
- return 0
492
- })
493
- }
494
-
495
- getPluginByName(pluginName: string): Plugin | undefined {
496
- const plugins = [...this.#plugins]
497
-
498
- return plugins.find((item) => item.name === pluginName)
505
+ getPlugin<TName extends keyof Kubb.PluginRegistry>(pluginName: TName): Plugin<Kubb.PluginRegistry[TName]> | undefined
506
+ getPlugin<TOptions extends PluginFactoryOptions = PluginFactoryOptions>(pluginName: string): Plugin<TOptions> | undefined
507
+ getPlugin(pluginName: string): Plugin | undefined {
508
+ return this.plugins.get(pluginName) as Plugin | undefined
499
509
  }
500
510
 
501
- getPluginsByName(hookName: keyof PluginWithLifeCycle, pluginName: string): Plugin[] {
502
- const plugins = [...this.plugins]
503
-
504
- const pluginByPluginName = plugins.filter((plugin) => hookName in plugin).filter((item) => item.name === pluginName)
505
-
506
- if (!pluginByPluginName?.length) {
507
- // fallback on the core plugin when there is no match
508
-
509
- const corePlugin = plugins.find((plugin) => plugin.name === CORE_PLUGIN_NAME && hookName in plugin)
510
-
511
- return corePlugin ? [corePlugin] : []
511
+ /**
512
+ * Like `getPlugin` but throws a descriptive error when the plugin is not found.
513
+ */
514
+ requirePlugin<TName extends keyof Kubb.PluginRegistry>(pluginName: TName): Plugin<Kubb.PluginRegistry[TName]>
515
+ requirePlugin<TOptions extends PluginFactoryOptions = PluginFactoryOptions>(pluginName: string): Plugin<TOptions>
516
+ requirePlugin(pluginName: string): Plugin {
517
+ const plugin = this.plugins.get(pluginName)
518
+ if (!plugin) {
519
+ throw new Error(`[kubb] Plugin "${pluginName}" is required but not found. Make sure it is included in your Kubb config.`)
512
520
  }
513
-
514
- return pluginByPluginName
521
+ return plugin
515
522
  }
516
523
 
517
524
  /**
@@ -647,22 +654,4 @@ export class PluginManager {
647
654
  return null
648
655
  }
649
656
  }
650
-
651
- #parse(plugin: UserPlugin): Plugin {
652
- const usedPluginNames = this.#usedPluginNames
653
-
654
- setUniqueName(plugin.name, usedPluginNames)
655
-
656
- const usageCount = usedPluginNames[plugin.name]
657
- if (usageCount && usageCount > 1) {
658
- throw new ValidationPluginError(
659
- `Duplicate plugin "${plugin.name}" detected. Each plugin can only be used once. Use a different configuration instead of adding multiple instances of the same plugin.`,
660
- )
661
- }
662
-
663
- return {
664
- install() {},
665
- ...plugin,
666
- } as unknown as Plugin
667
- }
668
657
  }