@kubb/core 1.0.0-alpha.6 → 1.0.0-alpha.8
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 +8 -4
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +17 -20
- package/dist/index.js +8 -4
- package/dist/index.js.map +1 -1
- package/package.json +6 -3
- package/src/build.ts +103 -0
- package/src/config.ts +15 -0
- package/src/generators/Generator.ts +23 -0
- package/src/generators/SchemaGenerator.ts +7 -0
- package/src/generators/index.ts +2 -0
- package/src/index.ts +11 -0
- package/src/managers/fileManager/FileManager.ts +162 -0
- package/src/managers/fileManager/TreeNode.ts +111 -0
- package/src/managers/fileManager/index.ts +3 -0
- package/src/managers/fileManager/types.ts +24 -0
- package/src/managers/index.ts +2 -0
- package/src/managers/pluginManager/PluginManager.ts +249 -0
- package/src/managers/pluginManager/index.ts +3 -0
- package/src/managers/pluginManager/types.ts +8 -0
- package/src/managers/pluginManager/validate.ts +21 -0
- package/src/plugin.ts +69 -0
- package/src/types.ts +193 -0
- package/src/utils/cache.ts +32 -0
- package/src/utils/getUniqueName.ts +10 -0
- package/src/utils/index.ts +11 -0
- package/src/utils/isPromise.ts +5 -0
- package/src/utils/isURL.ts +11 -0
- package/src/utils/jsdoc.ts +13 -0
- package/src/utils/nameSorter.ts +9 -0
- package/src/utils/objectToParameters.ts +19 -0
- package/src/utils/queue.ts +46 -0
- package/src/utils/read.ts +48 -0
- package/src/utils/timeout.ts +7 -0
- package/src/utils/write.ts +40 -0
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import dirTree from 'directory-tree'
|
|
2
|
+
|
|
3
|
+
import type { DirectoryTree, DirectoryTreeOptions } from 'directory-tree'
|
|
4
|
+
|
|
5
|
+
export type TreeNodeOptions = DirectoryTreeOptions
|
|
6
|
+
|
|
7
|
+
export class TreeNode<T = unknown> {
|
|
8
|
+
public data: T
|
|
9
|
+
|
|
10
|
+
public parent?: TreeNode<T>
|
|
11
|
+
|
|
12
|
+
public children: Array<TreeNode<T>> = []
|
|
13
|
+
|
|
14
|
+
constructor(data: T, parent?: TreeNode<T>) {
|
|
15
|
+
this.data = data
|
|
16
|
+
this.parent = parent
|
|
17
|
+
return this
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
addChild(data: T): TreeNode<T> {
|
|
21
|
+
const child = new TreeNode(data, this)
|
|
22
|
+
if (!this.children) {
|
|
23
|
+
this.children = []
|
|
24
|
+
}
|
|
25
|
+
this.children.push(child)
|
|
26
|
+
return child
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
find(data: T) {
|
|
30
|
+
if (data === this.data) {
|
|
31
|
+
return this
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (this.children) {
|
|
35
|
+
for (let i = 0, { length } = this.children, target: unknown = null; i < length; i++) {
|
|
36
|
+
target = this.children[i].find(data)
|
|
37
|
+
if (target) {
|
|
38
|
+
return target
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return null
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
leaves(): TreeNode<T>[] {
|
|
47
|
+
if (!this.children || this.children.length === 0) {
|
|
48
|
+
// this is a leaf
|
|
49
|
+
return [this]
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// if not a leaf, return all children's leaves recursively
|
|
53
|
+
const leaves: TreeNode<T>[] = []
|
|
54
|
+
if (this.children) {
|
|
55
|
+
for (let i = 0, { length } = this.children; i < length; i++) {
|
|
56
|
+
// eslint-disable-next-line prefer-spread
|
|
57
|
+
leaves.push.apply(leaves, this.children[i].leaves())
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return leaves
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
root(): TreeNode<T> {
|
|
64
|
+
if (!this.parent) {
|
|
65
|
+
return this
|
|
66
|
+
}
|
|
67
|
+
return this.parent.root()
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
forEach(callback: (treeNode: TreeNode<T>) => void): this {
|
|
71
|
+
if (typeof callback !== 'function') {
|
|
72
|
+
throw new TypeError('forEach() callback must be a function')
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// run this node through function
|
|
76
|
+
callback(this)
|
|
77
|
+
|
|
78
|
+
// do the same for all children
|
|
79
|
+
if (this.children) {
|
|
80
|
+
for (let i = 0, { length } = this.children; i < length; i++) {
|
|
81
|
+
this.children[i].forEach(callback)
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return this
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
public static build<T = unknown>(path: string, options: TreeNodeOptions = {}): TreeNode<T> | null {
|
|
89
|
+
const filteredTree = dirTree(path, { extensions: options?.extensions, exclude: options.exclude })
|
|
90
|
+
|
|
91
|
+
if (!filteredTree) {
|
|
92
|
+
return null
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const treeNode = new TreeNode({ name: filteredTree.name, path: filteredTree.path, type: filteredTree.type })
|
|
96
|
+
|
|
97
|
+
const recurse = (node: typeof treeNode, item: DirectoryTree) => {
|
|
98
|
+
const subNode = node.addChild({ name: item.name, path: item.path, type: item.type })
|
|
99
|
+
|
|
100
|
+
if (item.children?.length) {
|
|
101
|
+
item.children?.forEach((child) => {
|
|
102
|
+
recurse(subNode, child)
|
|
103
|
+
})
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
filteredTree.children?.forEach((child) => recurse(treeNode, child))
|
|
108
|
+
|
|
109
|
+
return treeNode as TreeNode<T>
|
|
110
|
+
}
|
|
111
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
type Import = {
|
|
2
|
+
name: string | string[]
|
|
3
|
+
path: string
|
|
4
|
+
isTypeOnly?: boolean
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export type File = {
|
|
8
|
+
/**
|
|
9
|
+
* Name to be used to dynamicly create the fileName(based on input.path)
|
|
10
|
+
*/
|
|
11
|
+
fileName: string
|
|
12
|
+
/**
|
|
13
|
+
* Path will be full qualified path to a specified file
|
|
14
|
+
*/
|
|
15
|
+
path: string
|
|
16
|
+
source: string
|
|
17
|
+
imports?: Import[]
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export type UUID = string
|
|
21
|
+
|
|
22
|
+
export type CacheStore = { id: UUID; file: File; status: Status }
|
|
23
|
+
|
|
24
|
+
export type Status = 'new' | 'success' | 'removed'
|
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
/* eslint-disable no-await-in-loop */
|
|
2
|
+
/* eslint-disable no-restricted-syntax */
|
|
3
|
+
|
|
4
|
+
import { definePlugin } from '../../plugin'
|
|
5
|
+
import { FileManager } from '../fileManager'
|
|
6
|
+
import { Queue } from '../../utils/queue'
|
|
7
|
+
|
|
8
|
+
import type { QueueTask } from '../../utils/queue'
|
|
9
|
+
import type { Argument0, Strategy } from './types'
|
|
10
|
+
import type { KubbConfig, KubbPlugin, PluginLifecycleHooks, PluginLifecycle, MaybePromise, ResolveIdParams } from '../../types'
|
|
11
|
+
import type { Logger } from '../../build'
|
|
12
|
+
import type { CorePluginOptions } from '../../plugin'
|
|
13
|
+
|
|
14
|
+
// inspired by: https://github.com/rollup/rollup/blob/master/src/utils/PluginDriver.ts#
|
|
15
|
+
|
|
16
|
+
// This will make sure no input hook is omitted
|
|
17
|
+
const hookNames: {
|
|
18
|
+
[P in PluginLifecycleHooks]: 1
|
|
19
|
+
} = {
|
|
20
|
+
validate: 1,
|
|
21
|
+
buildStart: 1,
|
|
22
|
+
resolveId: 1,
|
|
23
|
+
load: 1,
|
|
24
|
+
transform: 1,
|
|
25
|
+
writeFile: 1,
|
|
26
|
+
buildEnd: 1,
|
|
27
|
+
}
|
|
28
|
+
export const hooks = Object.keys(hookNames) as [PluginLifecycleHooks]
|
|
29
|
+
|
|
30
|
+
export class PluginManager {
|
|
31
|
+
public plugins: KubbPlugin[]
|
|
32
|
+
|
|
33
|
+
public readonly fileManager: FileManager
|
|
34
|
+
|
|
35
|
+
private readonly logger?: Logger
|
|
36
|
+
|
|
37
|
+
private readonly config: KubbConfig
|
|
38
|
+
|
|
39
|
+
public readonly core: KubbPlugin<CorePluginOptions>
|
|
40
|
+
|
|
41
|
+
public queue: Queue
|
|
42
|
+
|
|
43
|
+
constructor(config: KubbConfig, options: { logger?: Logger; task: QueueTask }) {
|
|
44
|
+
this.logger = options.logger
|
|
45
|
+
this.config = config
|
|
46
|
+
this.queue = new Queue(10)
|
|
47
|
+
|
|
48
|
+
this.fileManager = new FileManager({ task: options.task, queue: this.queue })
|
|
49
|
+
this.core = definePlugin({
|
|
50
|
+
config,
|
|
51
|
+
fileManager: this.fileManager,
|
|
52
|
+
load: this.load,
|
|
53
|
+
resolveId: this.resolveId,
|
|
54
|
+
}) as KubbPlugin<CorePluginOptions> & {
|
|
55
|
+
api: CorePluginOptions['api']
|
|
56
|
+
}
|
|
57
|
+
this.plugins = [this.core, ...(config.plugins || [])]
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
resolveId = (params: ResolveIdParams) => {
|
|
61
|
+
if (params.pluginName) {
|
|
62
|
+
return this.hookForPlugin(params.pluginName, 'resolveId', [params.fileName, params.directory, params.options])
|
|
63
|
+
}
|
|
64
|
+
return this.hookFirst('resolveId', [params.fileName, params.directory, params.options])
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
load = async (id: string) => {
|
|
68
|
+
return this.hookFirst('load', [id])
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// run only hook for a specific plugin name
|
|
72
|
+
hookForPlugin<H extends PluginLifecycleHooks>(
|
|
73
|
+
pluginName: string,
|
|
74
|
+
hookName: H,
|
|
75
|
+
parameters: Parameters<PluginLifecycle[H]>,
|
|
76
|
+
skipped?: ReadonlySet<KubbPlugin> | null
|
|
77
|
+
): Promise<ReturnType<PluginLifecycle[H]> | null> {
|
|
78
|
+
let promise: Promise<ReturnType<PluginLifecycle[H]> | null> = Promise.resolve(null)
|
|
79
|
+
for (const plugin of this.getSortedPlugins(hookName, pluginName)) {
|
|
80
|
+
if (skipped && skipped.has(plugin)) continue
|
|
81
|
+
promise = promise.then((result) => {
|
|
82
|
+
if (result != null) return result
|
|
83
|
+
return this.run('hookFirst', hookName, parameters, plugin) as typeof result
|
|
84
|
+
})
|
|
85
|
+
}
|
|
86
|
+
return promise
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// chains, first non-null result stops and returns
|
|
90
|
+
hookFirst<H extends PluginLifecycleHooks>(
|
|
91
|
+
hookName: H,
|
|
92
|
+
parameters: Parameters<PluginLifecycle[H]>,
|
|
93
|
+
skipped?: ReadonlySet<KubbPlugin> | null
|
|
94
|
+
): Promise<ReturnType<PluginLifecycle[H]> | null> {
|
|
95
|
+
let promise: Promise<ReturnType<PluginLifecycle[H]> | null> = Promise.resolve(null)
|
|
96
|
+
for (const plugin of this.getSortedPlugins(hookName)) {
|
|
97
|
+
if (skipped && skipped.has(plugin)) continue
|
|
98
|
+
promise = promise.then((result) => {
|
|
99
|
+
if (result != null) return result
|
|
100
|
+
return this.run('hookFirst', hookName, parameters, plugin) as typeof result
|
|
101
|
+
})
|
|
102
|
+
}
|
|
103
|
+
return promise
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// parallel
|
|
107
|
+
async hookParallel<H extends PluginLifecycleHooks, TOuput = void>(hookName: H, parameters?: Parameters<PluginLifecycle[H]> | undefined) {
|
|
108
|
+
const parallelPromises: Promise<TOuput>[] = []
|
|
109
|
+
|
|
110
|
+
for (const plugin of this.getSortedPlugins(hookName)) {
|
|
111
|
+
if ((plugin[hookName] as { sequential?: boolean })?.sequential) {
|
|
112
|
+
await Promise.all(parallelPromises)
|
|
113
|
+
parallelPromises.length = 0
|
|
114
|
+
await this.run('hookParallel', hookName, parameters, plugin)
|
|
115
|
+
} else {
|
|
116
|
+
const promise: Promise<TOuput> = this.run('hookParallel', hookName, parameters, plugin)
|
|
117
|
+
|
|
118
|
+
parallelPromises.push(promise)
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
return Promise.all(parallelPromises)
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// chains, reduces returned value, handling the reduced value as the first hook argument
|
|
125
|
+
hookReduceArg0<H extends PluginLifecycleHooks>(
|
|
126
|
+
hookName: H,
|
|
127
|
+
[argument0, ...rest]: Parameters<PluginLifecycle[H]>,
|
|
128
|
+
reduce: (reduction: Argument0<H>, result: ReturnType<PluginLifecycle[H]>, plugin: KubbPlugin) => MaybePromise<Argument0<H> | null>
|
|
129
|
+
): Promise<Argument0<H>> {
|
|
130
|
+
let promise: Promise<Argument0<H>> = Promise.resolve(argument0)
|
|
131
|
+
for (const plugin of this.getSortedPlugins(hookName)) {
|
|
132
|
+
promise = promise.then((argument0) =>
|
|
133
|
+
this.run('hookReduceArg0', hookName, [argument0, ...rest] as Parameters<PluginLifecycle[H]>, plugin).then((result) =>
|
|
134
|
+
reduce.call(this.core.api, argument0, result as ReturnType<PluginLifecycle[H]>, plugin)
|
|
135
|
+
)
|
|
136
|
+
) as Promise<Argument0<H>>
|
|
137
|
+
}
|
|
138
|
+
return promise
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// chains
|
|
142
|
+
|
|
143
|
+
hookSeq<H extends PluginLifecycleHooks>(hookName: H, parameters?: Parameters<PluginLifecycle[H]>) {
|
|
144
|
+
let promise: Promise<void> = Promise.resolve()
|
|
145
|
+
for (const plugin of this.getSortedPlugins(hookName)) {
|
|
146
|
+
promise = promise.then(() => this.run('hookSeq', hookName, parameters, plugin))
|
|
147
|
+
}
|
|
148
|
+
return promise.then(noReturn)
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
private getSortedPlugins(hookName: keyof PluginLifecycle, pluginName?: string): KubbPlugin[] {
|
|
152
|
+
const plugins = [...this.plugins]
|
|
153
|
+
|
|
154
|
+
if (pluginName) {
|
|
155
|
+
const pluginsByPluginName = plugins.filter((item) => item.name === pluginName && item[hookName])
|
|
156
|
+
if (pluginsByPluginName.length === 0) {
|
|
157
|
+
// fallback on the core plugin when there is no match
|
|
158
|
+
if (this.config.logLevel === 'warn' && this.logger?.spinner) {
|
|
159
|
+
this.logger.spinner.info(`Plugin hook with ${hookName} not found for plugin ${pluginName}`)
|
|
160
|
+
}
|
|
161
|
+
return [this.core]
|
|
162
|
+
}
|
|
163
|
+
return pluginsByPluginName
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
return plugins
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Run an async plugin hook and return the result.
|
|
171
|
+
* @param hookName Name of the plugin hook. Must be either in `PluginHooks` or `OutputPluginValueHooks`.
|
|
172
|
+
* @param args Arguments passed to the plugin hook.
|
|
173
|
+
* @param plugin The actual pluginObject to run.
|
|
174
|
+
*/
|
|
175
|
+
// Implementation signature
|
|
176
|
+
private run<H extends PluginLifecycleHooks, TResult = void>(
|
|
177
|
+
strategy: Strategy,
|
|
178
|
+
hookName: H,
|
|
179
|
+
parameters: unknown[] | undefined,
|
|
180
|
+
plugin: KubbPlugin
|
|
181
|
+
): Promise<TResult> {
|
|
182
|
+
const hook = plugin[hookName]!
|
|
183
|
+
|
|
184
|
+
return Promise.resolve()
|
|
185
|
+
.then(() => {
|
|
186
|
+
if (typeof hook !== 'function') {
|
|
187
|
+
return hook
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
if (this.config.logLevel === 'info' && this.logger?.spinner) {
|
|
191
|
+
this.logger.spinner.text = `[${strategy}] ${hookName}: Excecuting task for plugin ${plugin.name} \n`
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
const hookResult = (hook as Function).apply(this.core.api, parameters)
|
|
195
|
+
|
|
196
|
+
if (!(hookResult as Promise<unknown>)?.then) {
|
|
197
|
+
// short circuit for non-thenables and non-Promises
|
|
198
|
+
if (this.config.logLevel === 'info' && this.logger?.spinner) {
|
|
199
|
+
this.logger.spinner.succeed(`[${strategy}] ${hookName}: Excecuting task for plugin ${plugin.name} \n`)
|
|
200
|
+
}
|
|
201
|
+
return hookResult
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
return Promise.resolve(hookResult).then((result) => {
|
|
205
|
+
// action was fulfilled
|
|
206
|
+
if (this.config.logLevel === 'info' && this.logger?.spinner) {
|
|
207
|
+
this.logger.spinner.succeed(`[${strategy}] ${hookName}: Excecuting task for plugin ${plugin.name} \n`)
|
|
208
|
+
}
|
|
209
|
+
return result
|
|
210
|
+
})
|
|
211
|
+
})
|
|
212
|
+
.catch((e: Error) => {
|
|
213
|
+
this.catcher<H>(e, plugin, hookName)
|
|
214
|
+
}) as Promise<TResult>
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Run a sync plugin hook and return the result.
|
|
219
|
+
* @param hookName Name of the plugin hook. Must be in `PluginHooks`.
|
|
220
|
+
* @param args Arguments passed to the plugin hook.
|
|
221
|
+
* @param plugin The acutal plugin
|
|
222
|
+
* @param replaceContext When passed, the plugin context can be overridden.
|
|
223
|
+
*/
|
|
224
|
+
private runSync<H extends PluginLifecycleHooks>(
|
|
225
|
+
hookName: H,
|
|
226
|
+
parameters: Parameters<PluginLifecycle[H]>,
|
|
227
|
+
plugin: KubbPlugin
|
|
228
|
+
): ReturnType<PluginLifecycle[H]> | Error {
|
|
229
|
+
const hook = plugin[hookName]!
|
|
230
|
+
|
|
231
|
+
// const context = this.pluginContexts.get(plugin)!;
|
|
232
|
+
|
|
233
|
+
try {
|
|
234
|
+
// eslint-disable-next-line @typescript-eslint/ban-types
|
|
235
|
+
return (hook as Function).apply(this.core.api, parameters)
|
|
236
|
+
} catch (error) {
|
|
237
|
+
return error as Error
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
private catcher<H extends PluginLifecycleHooks>(e: Error, plugin: KubbPlugin, hookName: H) {
|
|
242
|
+
const text = `${e.message} (plugin: ${plugin.name}, hook: ${hookName})\n`
|
|
243
|
+
|
|
244
|
+
throw new Error(text, { cause: e })
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
|
249
|
+
function noReturn() {}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { PluginLifecycle } from '../../types'
|
|
2
|
+
/**
|
|
3
|
+
* Get the type of the first argument in a function.
|
|
4
|
+
* @example Arg0<(a: string, b: number) => void> -> string
|
|
5
|
+
*/
|
|
6
|
+
export type Argument0<H extends keyof PluginLifecycle> = Parameters<PluginLifecycle[H]>[0]
|
|
7
|
+
|
|
8
|
+
export type Strategy = 'hookFirst' | 'hookForPlugin' | 'hookParallel' | 'hookReduceArg0' | 'hookSeq'
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { KubbPlugin } from '../../types'
|
|
2
|
+
|
|
3
|
+
export class ValidationPluginError extends Error {}
|
|
4
|
+
|
|
5
|
+
export function validatePlugins(plugins: KubbPlugin[], dependedPluginNames: string | string[]): true {
|
|
6
|
+
let pluginNames: string[] = []
|
|
7
|
+
if (typeof dependedPluginNames === 'string') {
|
|
8
|
+
pluginNames = [dependedPluginNames]
|
|
9
|
+
} else {
|
|
10
|
+
pluginNames = dependedPluginNames
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
pluginNames.forEach((pluginName) => {
|
|
14
|
+
const exists = plugins.some((plugin) => plugin.name === pluginName)
|
|
15
|
+
if (!exists) {
|
|
16
|
+
throw new ValidationPluginError(`This plugin depends on the ${pluginName} plugin.`)
|
|
17
|
+
}
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
return true
|
|
21
|
+
}
|
package/src/plugin.ts
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import pathParser from 'path'
|
|
2
|
+
|
|
3
|
+
import { createPluginCache } from './utils'
|
|
4
|
+
|
|
5
|
+
import type { FileManager } from './managers/fileManager'
|
|
6
|
+
import type { PluginContext, KubbPlugin, PluginFactoryOptions } from './types'
|
|
7
|
+
|
|
8
|
+
type KubbPluginFactory<T extends PluginFactoryOptions = PluginFactoryOptions> = (
|
|
9
|
+
options: T['options']
|
|
10
|
+
) => T['nested'] extends true ? Array<KubbPlugin<T>> : KubbPlugin<T>
|
|
11
|
+
|
|
12
|
+
export function createPlugin<T extends PluginFactoryOptions = PluginFactoryOptions>(factory: KubbPluginFactory<T>) {
|
|
13
|
+
return (options: T['options']) => {
|
|
14
|
+
const plugin = factory(options)
|
|
15
|
+
if (Array.isArray(plugin)) {
|
|
16
|
+
throw new Error('Not implemented')
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// default transform
|
|
20
|
+
if (!plugin.transform) {
|
|
21
|
+
plugin.transform = function transform(code) {
|
|
22
|
+
return code
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
return plugin
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
type Options = {
|
|
31
|
+
config: PluginContext['config']
|
|
32
|
+
fileManager: FileManager
|
|
33
|
+
resolveId: PluginContext['resolveId']
|
|
34
|
+
load: PluginContext['load']
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// not publicly exported
|
|
38
|
+
export type CorePluginOptions = PluginFactoryOptions<Options, false, PluginContext>
|
|
39
|
+
|
|
40
|
+
export const name = 'core' as const
|
|
41
|
+
|
|
42
|
+
export const definePlugin = createPlugin<CorePluginOptions>((options) => {
|
|
43
|
+
const { fileManager, resolveId, load } = options
|
|
44
|
+
|
|
45
|
+
const api: PluginContext = {
|
|
46
|
+
get config() {
|
|
47
|
+
return options.config
|
|
48
|
+
},
|
|
49
|
+
fileManager,
|
|
50
|
+
async addFile(file) {
|
|
51
|
+
return fileManager.addOrAppend(file)
|
|
52
|
+
},
|
|
53
|
+
resolveId,
|
|
54
|
+
load,
|
|
55
|
+
cache: createPluginCache(Object.create(null)),
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return {
|
|
59
|
+
name,
|
|
60
|
+
options,
|
|
61
|
+
api,
|
|
62
|
+
resolveId(fileName, directory) {
|
|
63
|
+
if (!directory) {
|
|
64
|
+
return null
|
|
65
|
+
}
|
|
66
|
+
return pathParser.resolve(directory, fileName)
|
|
67
|
+
},
|
|
68
|
+
}
|
|
69
|
+
})
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
import type { FileManager, File } from './managers/fileManager'
|
|
2
|
+
import type { Cache } from './utils/cache'
|
|
3
|
+
|
|
4
|
+
export type MaybePromise<T> = Promise<T> | T
|
|
5
|
+
|
|
6
|
+
export type KubbUserConfig = Omit<KubbConfig, 'root'> & {
|
|
7
|
+
/**
|
|
8
|
+
* Project root directory. Can be an absolute path, or a path relative from
|
|
9
|
+
* the location of the config file itself.
|
|
10
|
+
* @default process.cwd()
|
|
11
|
+
*/
|
|
12
|
+
root?: string
|
|
13
|
+
/**
|
|
14
|
+
* Plugin type can be KubbJSONPlugin or KubbPlugin
|
|
15
|
+
* Example: ['@kubb/swagger', { output: false }]
|
|
16
|
+
* Or: createSwagger({ output: false })
|
|
17
|
+
*/
|
|
18
|
+
plugins?: Array<unknown>
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Global/internal config used through out the full generation.
|
|
23
|
+
*/
|
|
24
|
+
export type KubbConfig = {
|
|
25
|
+
/**
|
|
26
|
+
* Project root directory. Can be an absolute path, or a path relative from
|
|
27
|
+
* the location of the config file itself.
|
|
28
|
+
* @default process.cwd()
|
|
29
|
+
*/
|
|
30
|
+
root: string
|
|
31
|
+
input: {
|
|
32
|
+
/**
|
|
33
|
+
* Path to be used as the input. Can be an absolute path, or a path relative from
|
|
34
|
+
* the defined root option.
|
|
35
|
+
*/
|
|
36
|
+
path: string
|
|
37
|
+
}
|
|
38
|
+
output: {
|
|
39
|
+
/**
|
|
40
|
+
* Path to be used to export all generated files. Can be an absolute path, or a path relative based of the defined root option.
|
|
41
|
+
*/
|
|
42
|
+
path: string
|
|
43
|
+
/**
|
|
44
|
+
* Remove previous generated files and folders.
|
|
45
|
+
*/
|
|
46
|
+
clean?: boolean
|
|
47
|
+
/**
|
|
48
|
+
* Enabled or disable the writing to the filesystem. This is being used for the playground.
|
|
49
|
+
* @default true
|
|
50
|
+
*/
|
|
51
|
+
write?: boolean
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Array of Kubb plugins to use.
|
|
55
|
+
* The plugin/package can forsee some options that you need to pass through.
|
|
56
|
+
* Sometimes a plugin is depended on another plugin, if that's the case you will get an error back from the plugin you installed.
|
|
57
|
+
*/
|
|
58
|
+
plugins?: KubbPlugin[]
|
|
59
|
+
/**
|
|
60
|
+
* Hooks that will be called when a specific action is triggered in Kubb.
|
|
61
|
+
*/
|
|
62
|
+
hooks?: {
|
|
63
|
+
/**
|
|
64
|
+
* Hook that will be triggerend at the end of all executions.
|
|
65
|
+
* Useful for running Prettier or Eslint to use your own linting structure.
|
|
66
|
+
*/
|
|
67
|
+
done?: string | string[]
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Log level to report when using the CLI
|
|
71
|
+
*/
|
|
72
|
+
logLevel?: LogLevel
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export type CLIOptions = {
|
|
76
|
+
config?: string
|
|
77
|
+
debug?: boolean
|
|
78
|
+
watch?: string
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// plugin
|
|
82
|
+
|
|
83
|
+
export type KubbPluginKind = 'schema' | 'controller'
|
|
84
|
+
|
|
85
|
+
export type KubbJSONPlugin = [string, Record<string, any>]
|
|
86
|
+
|
|
87
|
+
export type KubbPlugin<TOptions extends PluginFactoryOptions = PluginFactoryOptions> = {
|
|
88
|
+
/**
|
|
89
|
+
* Unique name used for the plugin
|
|
90
|
+
* @example @kubb/typescript
|
|
91
|
+
*/
|
|
92
|
+
name: string
|
|
93
|
+
/**
|
|
94
|
+
* Kind/type for the plugin
|
|
95
|
+
* Type 'schema' can be used for JSON schema's, TypeScript types, ...
|
|
96
|
+
* Type 'controller' can be used to create generate API calls, React-Query hooks, Axios controllers, ...
|
|
97
|
+
* @default undefined
|
|
98
|
+
*/
|
|
99
|
+
kind?: KubbPluginKind
|
|
100
|
+
/**
|
|
101
|
+
* Defined an api that can be used by other plugins
|
|
102
|
+
*/
|
|
103
|
+
api?: TOptions['api']
|
|
104
|
+
/**
|
|
105
|
+
* Options set for a specific plugin(see kubb.config.ts)
|
|
106
|
+
*/
|
|
107
|
+
options?: TOptions['options']
|
|
108
|
+
} & Partial<PluginLifecycle<TOptions>>
|
|
109
|
+
|
|
110
|
+
// use of type objects
|
|
111
|
+
export type PluginFactoryOptions<Options = unknown, Nested extends boolean = false, Api = any, ResolveIdOptions = Record<string, any>> = {
|
|
112
|
+
options: Options
|
|
113
|
+
resolveIdOptions: ResolveIdOptions
|
|
114
|
+
nested: Nested
|
|
115
|
+
api: Api
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export type PluginLifecycle<TOptions extends PluginFactoryOptions = PluginFactoryOptions> = {
|
|
119
|
+
/**
|
|
120
|
+
* Valdiate all plugins to see if their depended plugins are installed and configured.
|
|
121
|
+
* @type hookParallel
|
|
122
|
+
*/
|
|
123
|
+
validate: (this: Omit<PluginContext, 'addFile'>, plugins: KubbPlugin[]) => MaybePromise<true>
|
|
124
|
+
/**
|
|
125
|
+
* Start of the lifecycle of a plugin.
|
|
126
|
+
* @type hookParallel
|
|
127
|
+
*/
|
|
128
|
+
buildStart: (this: PluginContext, kubbConfig: KubbConfig) => MaybePromise<void>
|
|
129
|
+
/**
|
|
130
|
+
* Resolve to an id based on importee(example: `./Pet.ts`) and directory(example: `./models`).
|
|
131
|
+
* @type hookFirst
|
|
132
|
+
* @example ('./Pet.ts', './src/gen/')
|
|
133
|
+
*/
|
|
134
|
+
resolveId: (this: Omit<PluginContext, 'addFile'>, fileName: string, directory?: string, options?: TOptions['resolveIdOptions']) => OptionalPath
|
|
135
|
+
/**
|
|
136
|
+
* Makes it possible to run async logic to override the path defined previously by `resolveId`.
|
|
137
|
+
* @type hookFirst
|
|
138
|
+
*/
|
|
139
|
+
load: (this: Omit<PluginContext, 'addFile'>, path: Path) => MaybePromise<TransformResult | null>
|
|
140
|
+
/**
|
|
141
|
+
* Transform the source-code.
|
|
142
|
+
* @type hookReduceArg0
|
|
143
|
+
*/
|
|
144
|
+
transform: (this: Omit<PluginContext, 'addFile'>, source: string, path: Path) => MaybePromise<TransformResult>
|
|
145
|
+
/**
|
|
146
|
+
* Write the result to the file-system based on the id(defined by `resolveId` or changed by `load`).
|
|
147
|
+
* @type hookParallel
|
|
148
|
+
*/
|
|
149
|
+
writeFile: (this: Omit<PluginContext, 'addFile'>, source: string | undefined, path: Path) => MaybePromise<void>
|
|
150
|
+
/**
|
|
151
|
+
* End of the plugin lifecycle.
|
|
152
|
+
* @type hookParallel
|
|
153
|
+
*/
|
|
154
|
+
buildEnd: (this: Omit<PluginContext, 'addFile'>) => MaybePromise<void>
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
export type PluginLifecycleHooks = keyof PluginLifecycle
|
|
158
|
+
|
|
159
|
+
export type ResolveIdParams<TOptions = Record<string, any>> = {
|
|
160
|
+
fileName: string
|
|
161
|
+
directory?: string | undefined
|
|
162
|
+
/**
|
|
163
|
+
* When set, resolveId will only call resolveId of the name of the plugin set here.
|
|
164
|
+
* If not defined it will fall back on the resolveId of the core plugin.
|
|
165
|
+
*/
|
|
166
|
+
pluginName?: string
|
|
167
|
+
/**
|
|
168
|
+
* Options to be passed to 'resolveId' 3th parameter
|
|
169
|
+
*/
|
|
170
|
+
options?: TOptions
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
export type PluginContext<TOptions = Record<string, any>> = {
|
|
174
|
+
config: KubbConfig
|
|
175
|
+
cache: Cache
|
|
176
|
+
fileManager: FileManager
|
|
177
|
+
addFile: (file: File) => Promise<File>
|
|
178
|
+
resolveId: (params: ResolveIdParams<TOptions>) => MaybePromise<OptionalPath>
|
|
179
|
+
load: (id: string) => MaybePromise<TransformResult | void>
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// null will mean clear the watcher for this key
|
|
183
|
+
export type TransformResult = string | null
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* @description Computing the name of a file or directory together with its position in relation to other directories traced back in a line to the root
|
|
187
|
+
*/
|
|
188
|
+
export type Path = string
|
|
189
|
+
export type OptionalPath = Path | null | undefined
|
|
190
|
+
export type FileName = string | null | undefined
|
|
191
|
+
|
|
192
|
+
export type LogType = 'error' | 'warn' | 'info'
|
|
193
|
+
export type LogLevel = LogType | 'silent'
|