@kubb/core 2.0.0-canary.20231030T124950 → 2.0.0
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/README.md +1 -1
- package/dist/chunk-4A7WG6IA.js +128 -0
- package/dist/chunk-4A7WG6IA.js.map +1 -0
- package/dist/chunk-54P4AWHI.js +71 -0
- package/dist/chunk-54P4AWHI.js.map +1 -0
- package/dist/chunk-5TK7TMV6.cjs +131 -0
- package/dist/chunk-5TK7TMV6.cjs.map +1 -0
- package/dist/chunk-7S67BJXQ.js +85 -0
- package/dist/chunk-7S67BJXQ.js.map +1 -0
- package/dist/chunk-E3ANGQ5N.cjs +2290 -0
- package/dist/chunk-E3ANGQ5N.cjs.map +1 -0
- package/dist/chunk-H47IKRXJ.cjs +129 -0
- package/dist/chunk-H47IKRXJ.cjs.map +1 -0
- package/dist/chunk-HIE46T3F.js +129 -0
- package/dist/chunk-HIE46T3F.js.map +1 -0
- package/dist/chunk-K2H7BYQB.js +155 -0
- package/dist/chunk-K2H7BYQB.js.map +1 -0
- package/dist/chunk-NAWI7UXW.js +67 -0
- package/dist/chunk-NAWI7UXW.js.map +1 -0
- package/dist/chunk-PLVKILIY.cjs +162 -0
- package/dist/chunk-PLVKILIY.cjs.map +1 -0
- package/dist/chunk-W2FP7ZWW.cjs +71 -0
- package/dist/chunk-W2FP7ZWW.cjs.map +1 -0
- package/dist/chunk-WZQO3EPM.cjs +91 -0
- package/dist/chunk-WZQO3EPM.cjs.map +1 -0
- package/dist/chunk-XDHI63G7.cjs +104 -0
- package/dist/chunk-XDHI63G7.cjs.map +1 -0
- package/dist/chunk-XPOF4D5N.js +18 -0
- package/dist/chunk-XPOF4D5N.js.map +1 -0
- package/dist/fs.cjs +31 -0
- package/dist/fs.cjs.map +1 -0
- package/dist/fs.d.cts +5 -0
- package/dist/fs.d.ts +5 -0
- package/dist/fs.js +11 -0
- package/dist/fs.js.map +1 -0
- package/dist/index.cjs +1866 -977
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +302 -319
- package/dist/index.d.ts +302 -319
- package/dist/index.js +1071 -846
- package/dist/index.js.map +1 -1
- package/dist/logger.cjs +26 -0
- package/dist/logger.cjs.map +1 -0
- package/dist/logger.d.cts +32 -0
- package/dist/logger.d.ts +32 -0
- package/dist/logger.js +8 -0
- package/dist/logger.js.map +1 -0
- package/dist/transformers.cjs +124 -0
- package/dist/transformers.cjs.map +1 -0
- package/dist/transformers.d.cts +55 -0
- package/dist/transformers.d.ts +55 -0
- package/dist/transformers.js +95 -0
- package/dist/transformers.js.map +1 -0
- package/dist/utils.cjs +23 -1163
- package/dist/utils.cjs.map +1 -1
- package/dist/utils.d.cts +2 -143
- package/dist/utils.d.ts +2 -143
- package/dist/utils.js +15 -1118
- package/dist/utils.js.map +1 -1
- package/dist/write-A6VgHkYA.d.cts +10 -0
- package/dist/write-A6VgHkYA.d.ts +10 -0
- package/package.json +40 -23
- package/src/BarrelManager.ts +113 -0
- package/src/FileManager.ts +581 -0
- package/src/Generator.ts +34 -0
- package/src/PackageManager.ts +178 -0
- package/src/PluginManager.ts +645 -0
- package/src/PromiseManager.ts +51 -0
- package/src/build.ts +221 -0
- package/src/config.ts +22 -0
- package/src/errors.ts +12 -0
- package/src/fs/clean.ts +5 -0
- package/src/fs/index.ts +3 -0
- package/src/fs/read.ts +68 -0
- package/src/fs/write.ts +79 -0
- package/src/index.ts +27 -0
- package/src/logger.ts +121 -0
- package/src/plugin.ts +80 -0
- package/src/transformers/casing.ts +9 -0
- package/src/transformers/combineCodes.ts +3 -0
- package/src/transformers/createJSDocBlockText.ts +9 -0
- package/src/transformers/escape.ts +31 -0
- package/src/transformers/indent.ts +3 -0
- package/src/transformers/index.ts +36 -0
- package/src/transformers/nameSorter.ts +9 -0
- package/src/transformers/searchAndReplace.ts +25 -0
- package/src/transformers/transformReservedWord.ts +97 -0
- package/src/transformers/trim.ts +7 -0
- package/src/types.ts +334 -0
- package/src/utils/EventEmitter.ts +24 -0
- package/src/utils/FunctionParams.ts +86 -0
- package/src/utils/TreeNode.ts +125 -0
- package/src/utils/URLPath.ts +133 -0
- package/src/utils/cache.ts +35 -0
- package/src/utils/executeStrategies.ts +83 -0
- package/src/utils/index.ts +8 -0
- package/src/utils/promise.ts +13 -0
- package/src/utils/renderTemplate.ts +31 -0
- package/src/utils/timeout.ts +7 -0
- 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
|
+
}
|