@kubb/core 2.0.0-alpha.3 → 2.0.0-alpha.5

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 (53) hide show
  1. package/dist/index.cjs +70 -75
  2. package/dist/index.cjs.map +1 -1
  3. package/dist/index.d.cts +198 -215
  4. package/dist/index.d.ts +198 -215
  5. package/dist/index.js +70 -73
  6. package/dist/index.js.map +1 -1
  7. package/dist/utils.cjs +19 -8
  8. package/dist/utils.cjs.map +1 -1
  9. package/dist/utils.d.cts +6 -2
  10. package/dist/utils.d.ts +6 -2
  11. package/dist/utils.js +19 -8
  12. package/dist/utils.js.map +1 -1
  13. package/package.json +7 -5
  14. package/src/BarrelManager.ts +123 -0
  15. package/src/FileManager.ts +483 -0
  16. package/src/Generator.ts +34 -0
  17. package/src/PackageManager.ts +163 -0
  18. package/src/PluginManager.ts +644 -0
  19. package/src/PromiseManager.ts +47 -0
  20. package/src/SchemaGenerator.ts +8 -0
  21. package/src/build.ts +207 -0
  22. package/src/config.ts +22 -0
  23. package/src/errors.ts +12 -0
  24. package/src/index.ts +28 -0
  25. package/src/plugin.ts +80 -0
  26. package/src/types.ts +346 -0
  27. package/src/utils/EventEmitter.ts +24 -0
  28. package/src/utils/FunctionParams.ts +85 -0
  29. package/src/utils/Queue.ts +110 -0
  30. package/src/utils/TreeNode.ts +122 -0
  31. package/src/utils/URLPath.ts +128 -0
  32. package/src/utils/cache.ts +35 -0
  33. package/src/utils/clean.ts +5 -0
  34. package/src/utils/executeStrategies.ts +71 -0
  35. package/src/utils/index.ts +19 -0
  36. package/src/utils/logger.ts +76 -0
  37. package/src/utils/promise.ts +13 -0
  38. package/src/utils/randomColour.ts +39 -0
  39. package/src/utils/read.ts +68 -0
  40. package/src/utils/renderTemplate.ts +31 -0
  41. package/src/utils/throttle.ts +30 -0
  42. package/src/utils/timeout.ts +7 -0
  43. package/src/utils/transformers/combineCodes.ts +3 -0
  44. package/src/utils/transformers/createJSDocBlockText.ts +15 -0
  45. package/src/utils/transformers/escape.ts +31 -0
  46. package/src/utils/transformers/indent.ts +3 -0
  47. package/src/utils/transformers/index.ts +22 -0
  48. package/src/utils/transformers/nameSorter.ts +9 -0
  49. package/src/utils/transformers/searchAndReplace.ts +25 -0
  50. package/src/utils/transformers/transformReservedWord.ts +97 -0
  51. package/src/utils/transformers/trim.ts +3 -0
  52. package/src/utils/uniqueName.ts +20 -0
  53. package/src/utils/write.ts +63 -0
@@ -0,0 +1,644 @@
1
+ /* eslint-disable @typescript-eslint/ban-types, @typescript-eslint/no-unsafe-argument */
2
+
3
+ import { EventEmitter } from './utils/EventEmitter.ts'
4
+ import { LogLevel } from './utils/logger.ts'
5
+ import { Queue } from './utils/Queue.ts'
6
+ import { transformReservedWord } from './utils/transformers/transformReservedWord.ts'
7
+ import { setUniqueName } from './utils/uniqueName.ts'
8
+ import { ValidationPluginError } from './errors.ts'
9
+ import { FileManager } from './FileManager.ts'
10
+ import { definePlugin as defineCorePlugin } from './plugin.ts'
11
+ import { isPromise, isPromiseRejectedResult } from './PromiseManager.ts'
12
+ import { PromiseManager } from './PromiseManager.ts'
13
+
14
+ import type { PossiblePromise } from '@kubb/types'
15
+ import type { KubbFile } from './FileManager.ts'
16
+ import type { CorePluginOptions } from './plugin.ts'
17
+ import type {
18
+ GetPluginFactoryOptions,
19
+ KubbConfig,
20
+ KubbPlugin,
21
+ KubbPluginWithLifeCycle,
22
+ KubbUserPlugin,
23
+ KubbUserPluginWithLifeCycle,
24
+ PluginFactoryOptions,
25
+ PluginLifecycle,
26
+ PluginLifecycleHooks,
27
+ PluginParameter,
28
+ ResolveNameParams,
29
+ ResolvePathParams,
30
+ } from './types.ts'
31
+ import type { Logger } from './utils/logger.ts'
32
+ import type { QueueJob } from './utils/Queue.ts'
33
+
34
+ type RequiredPluginLifecycle = Required<PluginLifecycle>
35
+
36
+ /**
37
+ * Get the type of the first argument in a function.
38
+ * @example Arg0<(a: string, b: number) => void> -> string
39
+ */
40
+ type Argument0<H extends keyof PluginLifecycle> = Parameters<RequiredPluginLifecycle[H]>[0]
41
+
42
+ type Strategy = 'hookFirst' | 'hookForPlugin' | 'hookParallel' | 'hookReduceArg0' | 'hookSeq'
43
+
44
+ type Executer<H extends PluginLifecycleHooks = PluginLifecycleHooks> = {
45
+ strategy: Strategy
46
+ hookName: H
47
+ plugin: KubbPlugin
48
+ parameters?: unknown[] | undefined
49
+ output?: unknown
50
+ }
51
+
52
+ type ParseResult<H extends PluginLifecycleHooks> = RequiredPluginLifecycle[H]
53
+
54
+ type SafeParseResult<H extends PluginLifecycleHooks, Result = ReturnType<ParseResult<H>>> = {
55
+ result: Result
56
+ plugin: KubbPlugin
57
+ }
58
+
59
+ // inspired by: https://github.com/rollup/rollup/blob/master/src/utils/PluginDriver.ts#
60
+
61
+ type Options = {
62
+ logger: Logger
63
+
64
+ /**
65
+ * Task for the FileManager
66
+ */
67
+ task: QueueJob<KubbFile.ResolvedFile>
68
+ /**
69
+ * Timeout between writes in the FileManager
70
+ */
71
+ writeTimeout?: number
72
+ }
73
+
74
+ type Events = {
75
+ execute: [executer: Executer]
76
+ executed: [executer: Executer]
77
+ error: [error: Error]
78
+ }
79
+
80
+ export class PluginManager {
81
+ readonly plugins: KubbPluginWithLifeCycle[]
82
+ readonly fileManager: FileManager
83
+ readonly eventEmitter: EventEmitter<Events> = new EventEmitter()
84
+
85
+ readonly queue: Queue
86
+ readonly config: KubbConfig
87
+
88
+ readonly executed: Executer[] = []
89
+ readonly logger: Logger
90
+ readonly #core: KubbPlugin<CorePluginOptions>
91
+
92
+ readonly #usedPluginNames: Record<string, number> = {}
93
+ readonly #promiseManager: PromiseManager
94
+
95
+ constructor(config: KubbConfig, options: Options) {
96
+ this.config = config
97
+ this.logger = options.logger
98
+ this.queue = new Queue(100, this.logger.logLevel === LogLevel.debug)
99
+ this.fileManager = new FileManager({ task: options.task, queue: this.queue, timeout: options.writeTimeout })
100
+ this.#promiseManager = new PromiseManager({ nullCheck: (state: SafeParseResult<'resolveName'> | null) => !!state?.result })
101
+
102
+ const plugins = config.plugins || []
103
+
104
+ const core = defineCorePlugin({
105
+ config,
106
+ logger: this.logger,
107
+ pluginManager: this,
108
+ fileManager: this.fileManager,
109
+ resolvePath: this.resolvePath.bind(this),
110
+ resolveName: this.resolveName.bind(this),
111
+ getPlugins: this.#getSortedPlugins.bind(this),
112
+ })
113
+
114
+ // call core.api.call with empty context so we can transform `api()` to `api: {}`
115
+ this.#core = this.#parse(core as unknown as KubbUserPlugin, this as any, core.api.call(null as any)) as KubbPlugin<CorePluginOptions>
116
+
117
+ this.plugins = [this.#core, ...plugins].map((plugin) => {
118
+ return this.#parse(plugin as KubbUserPlugin, this, this.#core.api)
119
+ })
120
+
121
+ return this
122
+ }
123
+
124
+ resolvePath = (params: ResolvePathParams): KubbFile.OptionalPath => {
125
+ if (params.pluginKey) {
126
+ const paths = this.hookForPluginSync({
127
+ pluginKey: params.pluginKey,
128
+ hookName: 'resolvePath',
129
+ parameters: [params.baseName, params.directory, params.options],
130
+ })
131
+
132
+ if (paths && paths?.length > 1) {
133
+ throw new Error(
134
+ `Cannot return a path where the 'pluginKey' ${params.pluginKey ? JSON.stringify(params.pluginKey) : '"'} is not unique enough\n\nPaths: ${
135
+ JSON.stringify(paths, undefined, 2)
136
+ }`,
137
+ )
138
+ }
139
+
140
+ return paths?.at(0)
141
+ }
142
+ return this.hookFirstSync({
143
+ hookName: 'resolvePath',
144
+ parameters: [params.baseName, params.directory, params.options],
145
+ }).result
146
+ }
147
+
148
+ resolveName = (params: ResolveNameParams): string => {
149
+ if (params.pluginKey) {
150
+ const names = this.hookForPluginSync({
151
+ pluginKey: params.pluginKey,
152
+ hookName: 'resolveName',
153
+ parameters: [params.name, params.type],
154
+ })
155
+
156
+ if (names && names?.length > 1) {
157
+ throw new Error(
158
+ `Cannot return a name where the 'pluginKey' ${params.pluginKey ? JSON.stringify(params.pluginKey) : '"'} is not unique enough\n\nNames: ${
159
+ JSON.stringify(names, undefined, 2)
160
+ }`,
161
+ )
162
+ }
163
+
164
+ return transformReservedWord(names?.at(0) || params.name)
165
+ }
166
+
167
+ const name = this.hookFirstSync({
168
+ hookName: 'resolveName',
169
+ parameters: [params.name, params.type],
170
+ }).result
171
+
172
+ return transformReservedWord(name)
173
+ }
174
+
175
+ on<TEventName extends keyof Events & string>(eventName: TEventName, handler: (...eventArg: Events[TEventName]) => void): void {
176
+ this.eventEmitter.on(eventName, handler as any)
177
+ }
178
+
179
+ /**
180
+ * Run only hook for a specific plugin name
181
+ */
182
+ hookForPlugin<H extends PluginLifecycleHooks>({
183
+ pluginKey,
184
+ hookName,
185
+ parameters,
186
+ }: {
187
+ pluginKey: KubbPlugin['key']
188
+ hookName: H
189
+ parameters: PluginParameter<H>
190
+ }): Promise<Array<ReturnType<ParseResult<H>> | null>> | null {
191
+ const plugins = this.getPluginsByKey(hookName, pluginKey)
192
+
193
+ const promises = plugins
194
+ .map((plugin) => {
195
+ return this.#execute<H>({
196
+ strategy: 'hookFirst',
197
+ hookName,
198
+ parameters,
199
+ plugin,
200
+ })
201
+ })
202
+ .filter(Boolean)
203
+
204
+ return Promise.all(promises)
205
+ }
206
+
207
+ hookForPluginSync<H extends PluginLifecycleHooks>({
208
+ pluginKey,
209
+ hookName,
210
+ parameters,
211
+ }: {
212
+ pluginKey: KubbPlugin['key']
213
+ hookName: H
214
+ parameters: PluginParameter<H>
215
+ }): Array<ReturnType<ParseResult<H>>> | null {
216
+ const plugins = this.getPluginsByKey(hookName, pluginKey)
217
+
218
+ return plugins
219
+ .map((plugin) => {
220
+ return this.#executeSync<H>({
221
+ strategy: 'hookFirst',
222
+ hookName,
223
+ parameters,
224
+ plugin,
225
+ })
226
+ })
227
+ .filter(Boolean)
228
+ }
229
+
230
+ /**
231
+ * Chains, first non-null result stops and returns
232
+ */
233
+ async hookFirst<H extends PluginLifecycleHooks>({
234
+ hookName,
235
+ parameters,
236
+ skipped,
237
+ }: {
238
+ hookName: H
239
+ parameters: PluginParameter<H>
240
+ skipped?: ReadonlySet<KubbPlugin> | null
241
+ }): Promise<SafeParseResult<H>> {
242
+ const promises = this.#getSortedPlugins().filter(plugin => {
243
+ return skipped ? skipped.has(plugin) : true
244
+ }).map((plugin) => {
245
+ return async () => {
246
+ const value = await this.#execute<H>({
247
+ strategy: 'hookFirst',
248
+ hookName,
249
+ parameters,
250
+ plugin,
251
+ })
252
+
253
+ return Promise.resolve(
254
+ {
255
+ plugin,
256
+ result: value,
257
+ } as SafeParseResult<H>,
258
+ )
259
+ }
260
+ })
261
+
262
+ return this.#promiseManager.run('first', promises)
263
+ }
264
+
265
+ /**
266
+ * Chains, first non-null result stops and returns
267
+ */
268
+ hookFirstSync<H extends PluginLifecycleHooks>({
269
+ hookName,
270
+ parameters,
271
+ skipped,
272
+ }: {
273
+ hookName: H
274
+ parameters: PluginParameter<H>
275
+ skipped?: ReadonlySet<KubbPlugin> | null
276
+ }): SafeParseResult<H> {
277
+ let parseResult: SafeParseResult<H> = null as unknown as SafeParseResult<H>
278
+
279
+ for (const plugin of this.#getSortedPlugins()) {
280
+ if (skipped && skipped.has(plugin)) {
281
+ continue
282
+ }
283
+
284
+ parseResult = {
285
+ result: this.#executeSync<H>({
286
+ strategy: 'hookFirst',
287
+ hookName,
288
+ parameters,
289
+ plugin,
290
+ }),
291
+ plugin,
292
+ } as SafeParseResult<H>
293
+
294
+ if (parseResult?.result != null) {
295
+ break
296
+ }
297
+ }
298
+ return parseResult
299
+ }
300
+
301
+ /**
302
+ * Parallel, runs all plugins
303
+ */
304
+ async hookParallel<H extends PluginLifecycleHooks, TOuput = void>({
305
+ hookName,
306
+ parameters,
307
+ }: {
308
+ hookName: H
309
+ parameters?: Parameters<RequiredPluginLifecycle[H]> | undefined
310
+ }): Promise<Awaited<TOuput>[]> {
311
+ const parallelPromises: Promise<TOuput>[] = []
312
+
313
+ for (const plugin of this.#getSortedPlugins()) {
314
+ // TODO implement sequential with `buildStart` as an object({ sequential: boolean; handler: PluginContext["buildStart"] })
315
+ // if ((plugin[hookName] as { sequential?: boolean })?.sequential) {
316
+ // await Promise.all(parallelPromises)
317
+ // parallelPromises.length = 0
318
+ // await this.execute({
319
+ // strategy: 'hookParallel',
320
+ // hookName,
321
+ // parameters,
322
+ // plugin,
323
+ // })
324
+ // }
325
+ const promise: Promise<TOuput> | null = this.#execute({ strategy: 'hookParallel', hookName, parameters, plugin }) as Promise<TOuput>
326
+
327
+ if (promise) {
328
+ parallelPromises.push(promise)
329
+ }
330
+ }
331
+ const results = await Promise.allSettled(parallelPromises)
332
+
333
+ results
334
+ .forEach((result, index) => {
335
+ if (isPromiseRejectedResult<Error>(result)) {
336
+ const plugin = this.#getSortedPlugins()[index]
337
+
338
+ this.#catcher<H>(result.reason, plugin, hookName)
339
+ }
340
+ })
341
+
342
+ return results.filter((result) => result.status === 'fulfilled').map((result) => (result as PromiseFulfilledResult<Awaited<TOuput>>).value)
343
+ }
344
+
345
+ /**
346
+ * Chains, reduces returned value, handling the reduced value as the first hook argument
347
+ */
348
+ hookReduceArg0<H extends PluginLifecycleHooks>({
349
+ hookName,
350
+ parameters,
351
+ reduce,
352
+ }: {
353
+ hookName: H
354
+ parameters: PluginParameter<H>
355
+ reduce: (reduction: Argument0<H>, result: ReturnType<ParseResult<H>>, plugin: KubbPlugin) => PossiblePromise<Argument0<H> | null>
356
+ }): Promise<Argument0<H>> {
357
+ const [argument0, ...rest] = parameters
358
+
359
+ let promise: Promise<Argument0<H>> = Promise.resolve(argument0)
360
+ for (const plugin of this.#getSortedPlugins()) {
361
+ promise = promise
362
+ .then((arg0) => {
363
+ const value = this.#execute({
364
+ strategy: 'hookReduceArg0',
365
+ hookName,
366
+ parameters: [arg0, ...rest] as PluginParameter<H>,
367
+ plugin,
368
+ })
369
+ return value
370
+ })
371
+ .then((result) => reduce.call(this.#core.api, argument0, result as ReturnType<ParseResult<H>>, plugin)) as Promise<Argument0<H>>
372
+ }
373
+ return promise
374
+ }
375
+
376
+ /**
377
+ * Chains plugins
378
+ */
379
+ async hookSeq<H extends PluginLifecycleHooks>({ hookName, parameters }: { hookName: H; parameters?: PluginParameter<H> }): Promise<void> {
380
+ const promises = this.#getSortedPlugins().map((plugin) => {
381
+ return () =>
382
+ this.#execute({
383
+ strategy: 'hookSeq',
384
+ hookName,
385
+ parameters,
386
+ plugin,
387
+ })
388
+ })
389
+
390
+ return this.#promiseManager.run('seq', promises)
391
+ }
392
+
393
+ #getSortedPlugins(hookName?: keyof PluginLifecycle): KubbPlugin[] {
394
+ const plugins = [...this.plugins].filter((plugin) => plugin.name !== 'core')
395
+
396
+ if (hookName) {
397
+ if (this.logger.logLevel === 'info') {
398
+ const containsHookName = plugins.some((item) => item[hookName])
399
+ if (!containsHookName) {
400
+ this.logger.warn(`No hook ${hookName} found`)
401
+ }
402
+ }
403
+
404
+ return plugins.filter((item) => item[hookName])
405
+ }
406
+
407
+ return plugins
408
+ }
409
+
410
+ getPluginsByKey(hookName: keyof PluginLifecycle, pluginKey: KubbPlugin['key']): KubbPlugin[] {
411
+ const plugins = [...this.plugins]
412
+ const [searchKind, searchPluginName, searchIdentifier] = pluginKey
413
+
414
+ const pluginByPluginName = plugins
415
+ .filter((plugin) => plugin[hookName])
416
+ .filter((item) => {
417
+ const [kind, name, identifier] = item.key
418
+
419
+ const identifierCheck = identifier?.toString() === searchIdentifier?.toString()
420
+ const kindCheck = kind === searchKind
421
+ const nameCheck = name === searchPluginName
422
+
423
+ if (searchIdentifier) {
424
+ return identifierCheck && kindCheck && nameCheck
425
+ }
426
+
427
+ return kindCheck && nameCheck
428
+ })
429
+
430
+ if (!pluginByPluginName?.length) {
431
+ // fallback on the core plugin when there is no match
432
+
433
+ const corePlugin = plugins.find((plugin) => plugin.name === 'core' && plugin[hookName])
434
+
435
+ if (this.logger.logLevel === 'info') {
436
+ if (corePlugin) {
437
+ this.logger.warn(`No hook '${hookName}' for pluginKey '${JSON.stringify(pluginKey)}' found, falling back on the '@kubb/core' plugin`)
438
+ } else {
439
+ this.logger.warn(`No hook '${hookName}' for pluginKey '${JSON.stringify(pluginKey)}' found, no fallback found in the '@kubb/core' plugin`)
440
+ }
441
+ }
442
+
443
+ return corePlugin ? [corePlugin] : []
444
+ }
445
+
446
+ return pluginByPluginName
447
+ }
448
+
449
+ #addExecutedToCallStack(executer: Executer | undefined) {
450
+ if (executer) {
451
+ this.eventEmitter.emit('executed', executer)
452
+ this.executed.push(executer)
453
+ }
454
+ }
455
+
456
+ /**
457
+ * Run an async plugin hook and return the result.
458
+ * @param hookName Name of the plugin hook. Must be either in `PluginHooks` or `OutputPluginValueHooks`.
459
+ * @param args Arguments passed to the plugin hook.
460
+ * @param plugin The actual pluginObject to run.
461
+ */
462
+ // Implementation signature
463
+ #execute<H extends PluginLifecycleHooks>({
464
+ strategy,
465
+ hookName,
466
+ parameters,
467
+ plugin,
468
+ }: {
469
+ strategy: Strategy
470
+ hookName: H
471
+ parameters: unknown[] | undefined
472
+ plugin: KubbPluginWithLifeCycle
473
+ }): Promise<ReturnType<ParseResult<H>> | null> | null {
474
+ const hook = plugin[hookName]
475
+ let output: unknown
476
+
477
+ if (!hook) {
478
+ return null
479
+ }
480
+
481
+ this.eventEmitter.emit('execute', { strategy, hookName, parameters, plugin })
482
+
483
+ const task = Promise.resolve()
484
+ .then(() => {
485
+ if (typeof hook === 'function') {
486
+ const possiblePromiseResult = (hook as Function).apply({ ...this.#core.api, plugin }, parameters) as Promise<ReturnType<ParseResult<H>>>
487
+
488
+ if (isPromise(possiblePromiseResult)) {
489
+ return Promise.resolve(possiblePromiseResult)
490
+ }
491
+ return possiblePromiseResult
492
+ }
493
+
494
+ return hook
495
+ })
496
+ .then((result) => {
497
+ output = result
498
+
499
+ this.#addExecutedToCallStack({
500
+ parameters,
501
+ output,
502
+ strategy,
503
+ hookName,
504
+ plugin,
505
+ })
506
+
507
+ return result
508
+ })
509
+ .catch((e: Error) => {
510
+ this.#catcher<H>(e, plugin, hookName)
511
+
512
+ return null
513
+ })
514
+
515
+ return task
516
+ }
517
+
518
+ /**
519
+ * Run a sync plugin hook and return the result.
520
+ * @param hookName Name of the plugin hook. Must be in `PluginHooks`.
521
+ * @param args Arguments passed to the plugin hook.
522
+ * @param plugin The acutal plugin
523
+ * @param replaceContext When passed, the plugin context can be overridden.
524
+ */
525
+ #executeSync<H extends PluginLifecycleHooks>({
526
+ strategy,
527
+ hookName,
528
+ parameters,
529
+ plugin,
530
+ }: {
531
+ strategy: Strategy
532
+ hookName: H
533
+ parameters: PluginParameter<H>
534
+ plugin: KubbPluginWithLifeCycle
535
+ }): ReturnType<ParseResult<H>> | null {
536
+ const hook = plugin[hookName]
537
+ let output: unknown
538
+
539
+ if (!hook) {
540
+ return null
541
+ }
542
+
543
+ this.eventEmitter.emit('execute', { strategy, hookName, parameters, plugin })
544
+
545
+ try {
546
+ if (typeof hook === 'function') {
547
+ const fn = (hook as Function).apply({ ...this.#core.api, plugin }, parameters) as ReturnType<ParseResult<H>>
548
+
549
+ output = fn
550
+ return fn
551
+ }
552
+
553
+ output = hook
554
+
555
+ this.#addExecutedToCallStack({
556
+ parameters,
557
+ output,
558
+ strategy,
559
+ hookName,
560
+ plugin,
561
+ })
562
+
563
+ return hook
564
+ } catch (e) {
565
+ this.#catcher<H>(e as Error, plugin, hookName)
566
+
567
+ return null
568
+ }
569
+ }
570
+
571
+ #catcher<H extends PluginLifecycleHooks>(e: Error, plugin?: KubbPlugin, hookName?: H) {
572
+ const text = `${e.message} (plugin: ${plugin?.name || 'unknown'}, hook: ${hookName || 'unknown'})\n`
573
+
574
+ this.logger.error(text)
575
+ this.eventEmitter.emit('error', e)
576
+ }
577
+
578
+ #parse<TPlugin extends KubbUserPluginWithLifeCycle>(
579
+ plugin: TPlugin,
580
+ pluginManager: PluginManager,
581
+ context: CorePluginOptions['api'] | undefined,
582
+ ): KubbPlugin<GetPluginFactoryOptions<TPlugin>> {
583
+ const usedPluginNames = pluginManager.#usedPluginNames
584
+
585
+ setUniqueName(plugin.name, usedPluginNames)
586
+
587
+ const key = plugin.key || ([plugin.kind, plugin.name, usedPluginNames[plugin.name]].filter(Boolean) as [typeof plugin.kind, typeof plugin.name, string])
588
+
589
+ if (plugin.name !== 'core' && usedPluginNames[plugin.name]! >= 2) {
590
+ pluginManager.logger.warn('Using multiple of the same plugin is an experimental feature')
591
+ }
592
+
593
+ // default transform
594
+ if (!plugin.transform) {
595
+ plugin.transform = function transform(code) {
596
+ return code
597
+ }
598
+ }
599
+
600
+ if (plugin.api && typeof plugin.api === 'function') {
601
+ const api = (plugin.api as Function).call(context) as typeof plugin.api
602
+
603
+ return {
604
+ ...plugin,
605
+ key,
606
+ api,
607
+ } as unknown as KubbPlugin<GetPluginFactoryOptions<TPlugin>>
608
+ }
609
+
610
+ return {
611
+ ...plugin,
612
+ key,
613
+ } as unknown as KubbPlugin<GetPluginFactoryOptions<TPlugin>>
614
+ }
615
+
616
+ static getDependedPlugins<
617
+ T1 extends PluginFactoryOptions,
618
+ T2 extends PluginFactoryOptions = never,
619
+ T3 extends PluginFactoryOptions = never,
620
+ TOutput = T3 extends never ? T2 extends never ? [T1: KubbPlugin<T1>]
621
+ : [T1: KubbPlugin<T1>, T2: KubbPlugin<T2>]
622
+ : [T1: KubbPlugin<T1>, T2: KubbPlugin<T2>, T3: KubbPlugin<T3>],
623
+ >(plugins: Array<KubbPlugin>, dependedPluginNames: string | string[]): TOutput {
624
+ let pluginNames: string[] = []
625
+ if (typeof dependedPluginNames === 'string') {
626
+ pluginNames = [dependedPluginNames]
627
+ } else {
628
+ pluginNames = dependedPluginNames
629
+ }
630
+
631
+ return pluginNames.map((pluginName) => {
632
+ const plugin = plugins.find((plugin) => plugin.name === pluginName)
633
+ if (!plugin) {
634
+ throw new ValidationPluginError(`This plugin depends on the ${pluginName} plugin.`)
635
+ }
636
+ return plugin
637
+ }) as TOutput
638
+ }
639
+
640
+ // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
641
+ static get hooks() {
642
+ return ['validate', 'buildStart', 'resolvePath', 'resolveName', 'load', 'transform', 'writeFile', 'buildEnd'] as const
643
+ }
644
+ }
@@ -0,0 +1,47 @@
1
+ import { hookFirst, hookSeq } from './utils/executeStrategies.ts'
2
+
3
+ import type { PossiblePromise } from '@kubb/types'
4
+ import type { Strategy, StrategySwitch } from './utils/executeStrategies.ts'
5
+
6
+ type PromiseFunc<T = unknown, T2 = never> = () => T2 extends never ? Promise<T> : Promise<T> | T2
7
+
8
+ type Options<TState = any> = {
9
+ nullCheck?: (state: TState) => boolean
10
+ }
11
+
12
+ export class PromiseManager<TState = any> {
13
+ #options: Options<TState> = {}
14
+
15
+ constructor(options: Options<TState> = {}) {
16
+ this.#options = options
17
+
18
+ return this
19
+ }
20
+
21
+ run<TInput extends Array<PromiseFunc<TValue, null>>, TValue, TStrategy extends Strategy, TOutput = StrategySwitch<TStrategy, TInput, TValue>>(
22
+ strategy: TStrategy,
23
+ promises: TInput,
24
+ ): TOutput {
25
+ if (strategy === 'seq') {
26
+ return hookSeq<TInput, TValue, TOutput>(promises)
27
+ }
28
+
29
+ if (strategy === 'first') {
30
+ return hookFirst<TInput, TValue, TOutput>(promises, this.#options.nullCheck)
31
+ }
32
+
33
+ throw new Error(`${strategy} not implemented`)
34
+ }
35
+ }
36
+
37
+ export function isPromise<T>(result: PossiblePromise<T>): result is Promise<T> {
38
+ return !!result && typeof (result as Promise<unknown>)?.then === 'function'
39
+ }
40
+
41
+ export function isPromiseFulfilledResult<T = unknown>(result: PromiseSettledResult<unknown>): result is PromiseFulfilledResult<T> {
42
+ return result.status === 'fulfilled'
43
+ }
44
+
45
+ export function isPromiseRejectedResult<T>(result: PromiseSettledResult<unknown>): result is Omit<PromiseRejectedResult, 'reason'> & { reason: T } {
46
+ return result.status === 'rejected'
47
+ }
@@ -0,0 +1,8 @@
1
+ import { Generator } from './Generator.ts'
2
+
3
+ /**
4
+ * Abstract class that contains the building blocks for plugins to create their own SchemaGenerator
5
+ */
6
+ export abstract class SchemaGenerator<TOptions extends object, TInput, TOutput> extends Generator<TOptions> {
7
+ abstract build(schema: TInput, name: string, description?: string): TOutput
8
+ }