@kubb/core 2.0.0-canary.20231030T124958 → 2.0.1

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