@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.
- package/dist/index.cjs +70 -75
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +198 -215
- package/dist/index.d.ts +198 -215
- package/dist/index.js +70 -73
- package/dist/index.js.map +1 -1
- package/dist/utils.cjs +19 -8
- package/dist/utils.cjs.map +1 -1
- package/dist/utils.d.cts +6 -2
- package/dist/utils.d.ts +6 -2
- package/dist/utils.js +19 -8
- package/dist/utils.js.map +1 -1
- package/package.json +7 -5
- package/src/BarrelManager.ts +123 -0
- package/src/FileManager.ts +483 -0
- package/src/Generator.ts +34 -0
- package/src/PackageManager.ts +163 -0
- package/src/PluginManager.ts +644 -0
- package/src/PromiseManager.ts +47 -0
- package/src/SchemaGenerator.ts +8 -0
- package/src/build.ts +207 -0
- package/src/config.ts +22 -0
- package/src/errors.ts +12 -0
- package/src/index.ts +28 -0
- package/src/plugin.ts +80 -0
- package/src/types.ts +346 -0
- package/src/utils/EventEmitter.ts +24 -0
- package/src/utils/FunctionParams.ts +85 -0
- package/src/utils/Queue.ts +110 -0
- package/src/utils/TreeNode.ts +122 -0
- package/src/utils/URLPath.ts +128 -0
- package/src/utils/cache.ts +35 -0
- package/src/utils/clean.ts +5 -0
- package/src/utils/executeStrategies.ts +71 -0
- package/src/utils/index.ts +19 -0
- package/src/utils/logger.ts +76 -0
- package/src/utils/promise.ts +13 -0
- package/src/utils/randomColour.ts +39 -0
- package/src/utils/read.ts +68 -0
- package/src/utils/renderTemplate.ts +31 -0
- package/src/utils/throttle.ts +30 -0
- package/src/utils/timeout.ts +7 -0
- package/src/utils/transformers/combineCodes.ts +3 -0
- package/src/utils/transformers/createJSDocBlockText.ts +15 -0
- package/src/utils/transformers/escape.ts +31 -0
- package/src/utils/transformers/indent.ts +3 -0
- package/src/utils/transformers/index.ts +22 -0
- package/src/utils/transformers/nameSorter.ts +9 -0
- package/src/utils/transformers/searchAndReplace.ts +25 -0
- package/src/utils/transformers/transformReservedWord.ts +97 -0
- package/src/utils/transformers/trim.ts +3 -0
- package/src/utils/uniqueName.ts +20 -0
- 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
|
+
}
|