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

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