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

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