@kubb/core 2.0.0-alpha.3 → 2.0.0-alpha.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +62 -74
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +18 -38
- package/dist/index.d.ts +18 -38
- package/dist/index.js +62 -72
- package/dist/index.js.map +1 -1
- package/dist/utils.cjs +12 -7
- package/dist/utils.cjs.map +1 -1
- package/dist/utils.d.cts +2 -1
- package/dist/utils.d.ts +2 -1
- package/dist/utils.js +12 -7
- package/dist/utils.js.map +1 -1
- package/package.json +8 -7
- package/src/BarrelManager.ts +123 -0
- package/src/FileManager.ts +482 -0
- package/src/Generator.ts +34 -0
- package/src/PackageManager.ts +163 -0
- package/src/PluginManager.ts +640 -0
- package/src/PromiseManager.ts +48 -0
- package/src/SchemaGenerator.ts +8 -0
- package/src/build.ts +198 -0
- package/src/config.ts +21 -0
- package/src/errors.ts +12 -0
- package/src/index.ts +28 -0
- package/src/plugin.ts +80 -0
- package/src/types.ts +370 -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 +20 -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/uniqueName.ts +20 -0
- package/src/utils/write.ts +63 -0
package/src/types.ts
ADDED
|
@@ -0,0 +1,370 @@
|
|
|
1
|
+
import type { FileManager, KubbFile } from './FileManager.ts'
|
|
2
|
+
import type { OptionsPlugins, PluginUnion } from './index.ts'
|
|
3
|
+
import type { PluginManager } from './PluginManager.ts'
|
|
4
|
+
import type { Cache } from './utils/cache.ts'
|
|
5
|
+
import type { Logger, LogLevel } from './utils/logger.ts'
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Config used in `kubb.config.js`
|
|
9
|
+
*
|
|
10
|
+
* @example import { defineConfig } from '@kubb/core'
|
|
11
|
+
* export default defineConfig({
|
|
12
|
+
* ...
|
|
13
|
+
* })
|
|
14
|
+
*/
|
|
15
|
+
export type KubbUserConfig = Omit<KubbConfig, 'root' | 'plugins'> & {
|
|
16
|
+
/**
|
|
17
|
+
* Project root directory. Can be an absolute path, or a path relative from
|
|
18
|
+
* the location of the config file itself.
|
|
19
|
+
* @default process.cwd()
|
|
20
|
+
*/
|
|
21
|
+
root?: string
|
|
22
|
+
/**
|
|
23
|
+
* Plugin type can be KubbJSONPlugin or KubbPlugin
|
|
24
|
+
* Example: ['@kubb/swagger', { output: false }]
|
|
25
|
+
* Or: createSwagger({ output: false })
|
|
26
|
+
*/
|
|
27
|
+
plugins?: Array<Omit<KubbUserPlugin, 'api'> | KubbUnionPlugins | [name: string, options: object]>
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export type InputPath = {
|
|
31
|
+
/**
|
|
32
|
+
* Path to be used as the input. This can be an absolute path or a path relative to the `root`.
|
|
33
|
+
*/
|
|
34
|
+
path: string
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export type InputData = {
|
|
38
|
+
/**
|
|
39
|
+
* `string` or `object` containing the data.
|
|
40
|
+
*/
|
|
41
|
+
data: string | unknown
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
type Input = InputPath | InputData
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* @private
|
|
48
|
+
*/
|
|
49
|
+
export type KubbConfig<TInput = Input> = {
|
|
50
|
+
/**
|
|
51
|
+
* Optional config name to show in CLI output
|
|
52
|
+
*/
|
|
53
|
+
name?: string
|
|
54
|
+
/**
|
|
55
|
+
* Project root directory. Can be an absolute path, or a path relative from
|
|
56
|
+
* the location of the config file itself.
|
|
57
|
+
* @default process.cwd()
|
|
58
|
+
*/
|
|
59
|
+
root: string
|
|
60
|
+
input: TInput
|
|
61
|
+
output: {
|
|
62
|
+
/**
|
|
63
|
+
* Path to be used to export all generated files.
|
|
64
|
+
* This can be an absolute path, or a path relative based of the defined `root` option.
|
|
65
|
+
*/
|
|
66
|
+
path: string
|
|
67
|
+
/**
|
|
68
|
+
* Clean output directory before each build.
|
|
69
|
+
*/
|
|
70
|
+
clean?: boolean
|
|
71
|
+
/**
|
|
72
|
+
* Write files to the fileSystem
|
|
73
|
+
* This is being used for the playground.
|
|
74
|
+
* @default true
|
|
75
|
+
*/
|
|
76
|
+
write?: boolean
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Array of Kubb plugins to use.
|
|
80
|
+
* The plugin/package can forsee some options that you need to pass through.
|
|
81
|
+
* Sometimes a plugin is depended on another plugin, if that's the case you will get an error back from the plugin you installed.
|
|
82
|
+
*/
|
|
83
|
+
plugins?: Array<KubbPlugin>
|
|
84
|
+
/**
|
|
85
|
+
* Hooks that will be called when a specific action is triggered in Kubb.
|
|
86
|
+
*/
|
|
87
|
+
hooks?: {
|
|
88
|
+
/**
|
|
89
|
+
* Hook that will be triggered at the end of all executions.
|
|
90
|
+
* Useful for running Prettier or ESLint to format/lint your code.
|
|
91
|
+
*/
|
|
92
|
+
done?: string | Array<string>
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export type CLIOptions = {
|
|
97
|
+
/**
|
|
98
|
+
* Path to `kubb.config.js`
|
|
99
|
+
*/
|
|
100
|
+
config?: string
|
|
101
|
+
/**
|
|
102
|
+
* Watch changes on input
|
|
103
|
+
*/
|
|
104
|
+
watch?: string
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Log level to report when using the CLI
|
|
108
|
+
*
|
|
109
|
+
* `silent` will hide all information that is not relevant
|
|
110
|
+
*
|
|
111
|
+
* `info` will show all information possible(not related to the PluginManager)
|
|
112
|
+
*
|
|
113
|
+
* `debug` will show all information possible(related to the PluginManager), handy for seeing logs
|
|
114
|
+
* @default `silent`
|
|
115
|
+
*/
|
|
116
|
+
logLevel?: LogLevel
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export type BuildOutput = {
|
|
120
|
+
files: FileManager['files']
|
|
121
|
+
pluginManager: PluginManager
|
|
122
|
+
/**
|
|
123
|
+
* Only for safeBuild
|
|
124
|
+
*/
|
|
125
|
+
error?: Error
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// plugin
|
|
129
|
+
|
|
130
|
+
export type KubbPluginKind = 'schema' | 'controller'
|
|
131
|
+
|
|
132
|
+
export type KubbUnionPlugins = PluginUnion
|
|
133
|
+
|
|
134
|
+
export type KubbObjectPlugin = keyof OptionsPlugins
|
|
135
|
+
|
|
136
|
+
export type GetPluginFactoryOptions<TPlugin extends KubbUserPlugin> = TPlugin extends KubbUserPlugin<infer X> ? X : never
|
|
137
|
+
|
|
138
|
+
export type KubbUserPlugin<TOptions extends PluginFactoryOptions = PluginFactoryOptions> =
|
|
139
|
+
& {
|
|
140
|
+
/**
|
|
141
|
+
* Unique name used for the plugin
|
|
142
|
+
* @example @kubb/typescript
|
|
143
|
+
*/
|
|
144
|
+
name: TOptions['name']
|
|
145
|
+
/**
|
|
146
|
+
* Internal key used when a developer uses more than one of the same plugin
|
|
147
|
+
* @private
|
|
148
|
+
*/
|
|
149
|
+
key?: TOptions['key']
|
|
150
|
+
/**
|
|
151
|
+
* Options set for a specific plugin(see kubb.config.js), passthrough of options.
|
|
152
|
+
*/
|
|
153
|
+
options: TOptions['options'] extends never ? undefined : TOptions['options']
|
|
154
|
+
}
|
|
155
|
+
& Partial<PluginLifecycle<TOptions>>
|
|
156
|
+
& (TOptions['api'] extends never ? {
|
|
157
|
+
api?: never
|
|
158
|
+
}
|
|
159
|
+
: {
|
|
160
|
+
api: (this: TOptions['name'] extends 'core' ? null : Omit<PluginContext, 'addFile'>) => TOptions['api']
|
|
161
|
+
})
|
|
162
|
+
& (TOptions['kind'] extends never ? {
|
|
163
|
+
kind?: never
|
|
164
|
+
}
|
|
165
|
+
: {
|
|
166
|
+
/**
|
|
167
|
+
* Kind/type for the plugin
|
|
168
|
+
* Type 'schema' can be used for JSON schema's, TypeScript types, ...
|
|
169
|
+
* Type 'controller' can be used to create generate API calls, React-Query hooks, Axios controllers, ...
|
|
170
|
+
* @default undefined
|
|
171
|
+
*/
|
|
172
|
+
kind: TOptions['kind']
|
|
173
|
+
})
|
|
174
|
+
|
|
175
|
+
export type KubbPlugin<TOptions extends PluginFactoryOptions = PluginFactoryOptions> =
|
|
176
|
+
& {
|
|
177
|
+
/**
|
|
178
|
+
* Unique name used for the plugin
|
|
179
|
+
* @example @kubb/typescript
|
|
180
|
+
*/
|
|
181
|
+
name: TOptions['name']
|
|
182
|
+
/**
|
|
183
|
+
* Internal key used when a developer uses more than one of the same plugin
|
|
184
|
+
* @private
|
|
185
|
+
*/
|
|
186
|
+
key: TOptions['key']
|
|
187
|
+
/**
|
|
188
|
+
* Options set for a specific plugin(see kubb.config.js), passthrough of options.
|
|
189
|
+
*/
|
|
190
|
+
options: TOptions['options'] extends never ? undefined : TOptions['options']
|
|
191
|
+
/**
|
|
192
|
+
* Kind/type for the plugin
|
|
193
|
+
* Type 'schema' can be used for JSON schema's, TypeScript types, ...
|
|
194
|
+
* Type 'controller' can be used to create generate API calls, React-Query hooks, Axios controllers, ...
|
|
195
|
+
* @default undefined
|
|
196
|
+
*/
|
|
197
|
+
kind?: KubbPluginKind
|
|
198
|
+
/**
|
|
199
|
+
* Define an api that can be used by other plugins, see `PluginManager' where we convert from `KubbUserPlugin` to `KubbPlugin`(used when calling `createPlugin`).
|
|
200
|
+
*/
|
|
201
|
+
}
|
|
202
|
+
& PluginLifecycle<TOptions>
|
|
203
|
+
& (TOptions['api'] extends never ? {
|
|
204
|
+
api?: never
|
|
205
|
+
}
|
|
206
|
+
: {
|
|
207
|
+
api: TOptions['api']
|
|
208
|
+
})
|
|
209
|
+
|
|
210
|
+
// use of type objects
|
|
211
|
+
|
|
212
|
+
export type PluginFactoryOptions<
|
|
213
|
+
Name = string,
|
|
214
|
+
Kind extends KubbPluginKind = KubbPluginKind | never,
|
|
215
|
+
Options = unknown | never,
|
|
216
|
+
Nested extends boolean = false,
|
|
217
|
+
API = unknown | never,
|
|
218
|
+
resolvePathOptions = Record<string, unknown>,
|
|
219
|
+
> = {
|
|
220
|
+
name: Name
|
|
221
|
+
kind: Kind
|
|
222
|
+
/**
|
|
223
|
+
* Same like `QueryKey` in `@tanstack/react-query`
|
|
224
|
+
*/
|
|
225
|
+
key: [kind: Kind | undefined, name: Name, identifier?: string | number]
|
|
226
|
+
options: Options
|
|
227
|
+
nested: Nested
|
|
228
|
+
api: API
|
|
229
|
+
resolvePathOptions: resolvePathOptions
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
export type PluginLifecycle<TOptions extends PluginFactoryOptions = PluginFactoryOptions> = {
|
|
233
|
+
/**
|
|
234
|
+
* Valdiate all plugins to see if their depended plugins are installed and configured.
|
|
235
|
+
* @type hookParallel
|
|
236
|
+
*/
|
|
237
|
+
validate?: (this: Omit<PluginContext, 'addFile'>, plugins: NonNullable<KubbConfig['plugins']>) => PossiblePromise<true>
|
|
238
|
+
/**
|
|
239
|
+
* Start of the lifecycle of a plugin.
|
|
240
|
+
* @type hookParallel
|
|
241
|
+
*/
|
|
242
|
+
buildStart?: (this: PluginContext, kubbConfig: KubbConfig) => PossiblePromise<void>
|
|
243
|
+
/**
|
|
244
|
+
* Resolve to a Path based on a baseName(example: `./Pet.ts`) and directory(example: `./models`).
|
|
245
|
+
* Options can als be included.
|
|
246
|
+
* @type hookFirst
|
|
247
|
+
* @example ('./Pet.ts', './src/gen/') => '/src/gen/Pet.ts'
|
|
248
|
+
*/
|
|
249
|
+
resolvePath?: (this: PluginContext, baseName: string, directory?: string, options?: TOptions['resolvePathOptions']) => KubbFile.OptionalPath
|
|
250
|
+
/**
|
|
251
|
+
* Resolve to a name based on a string.
|
|
252
|
+
* Useful when converting to PascalCase or camelCase.
|
|
253
|
+
* @type hookFirst
|
|
254
|
+
* @example ('pet') => 'Pet'
|
|
255
|
+
*/
|
|
256
|
+
resolveName?: (this: PluginContext, name: ResolveNameParams['name'], type?: ResolveNameParams['type']) => string
|
|
257
|
+
/**
|
|
258
|
+
* Makes it possible to run async logic to override the path defined previously by `resolvePath`.
|
|
259
|
+
* @type hookFirst
|
|
260
|
+
*/
|
|
261
|
+
load?: (this: Omit<PluginContext, 'addFile'>, path: KubbFile.Path) => PossiblePromise<TransformResult | null>
|
|
262
|
+
/**
|
|
263
|
+
* Transform the source-code.
|
|
264
|
+
* @type hookReduceArg0
|
|
265
|
+
*/
|
|
266
|
+
transform?: (this: Omit<PluginContext, 'addFile'>, source: string, path: KubbFile.Path) => PossiblePromise<TransformResult>
|
|
267
|
+
/**
|
|
268
|
+
* Write the result to the file-system based on the id(defined by `resolvePath` or changed by `load`).
|
|
269
|
+
* @type hookParallel
|
|
270
|
+
*/
|
|
271
|
+
writeFile?: (this: Omit<PluginContext, 'addFile'>, source: string | undefined, path: KubbFile.Path) => PossiblePromise<string | void>
|
|
272
|
+
/**
|
|
273
|
+
* End of the plugin lifecycle.
|
|
274
|
+
* @type hookParallel
|
|
275
|
+
*/
|
|
276
|
+
buildEnd?: (this: PluginContext) => PossiblePromise<void>
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
export type PluginLifecycleHooks = keyof PluginLifecycle
|
|
280
|
+
|
|
281
|
+
export type PluginParameter<H extends PluginLifecycleHooks> = Parameters<Required<PluginLifecycle>[H]>
|
|
282
|
+
|
|
283
|
+
export type PluginCache = Record<string, [number, unknown]>
|
|
284
|
+
|
|
285
|
+
export type ResolvePathParams<TOptions = Record<string, unknown>> = {
|
|
286
|
+
pluginKey?: KubbPlugin['key']
|
|
287
|
+
baseName: string
|
|
288
|
+
directory?: string | undefined
|
|
289
|
+
/**
|
|
290
|
+
* Options to be passed to 'resolvePath' 3th parameter
|
|
291
|
+
*/
|
|
292
|
+
options?: TOptions
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
export type ResolveNameParams = {
|
|
296
|
+
name: string
|
|
297
|
+
pluginKey?: KubbPlugin['key']
|
|
298
|
+
type?: 'file' | 'function'
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
export type PluginContext<TOptions = Record<string, unknown>> = {
|
|
302
|
+
config: KubbConfig
|
|
303
|
+
cache: Cache<PluginCache>
|
|
304
|
+
fileManager: FileManager
|
|
305
|
+
pluginManager: PluginManager
|
|
306
|
+
addFile: (...file: Array<KubbFile.File>) => Promise<Array<KubbFile.File>>
|
|
307
|
+
resolvePath: (params: ResolvePathParams<TOptions>) => KubbFile.OptionalPath
|
|
308
|
+
resolveName: (params: ResolveNameParams) => string
|
|
309
|
+
logger: Logger
|
|
310
|
+
/**
|
|
311
|
+
* All plugins
|
|
312
|
+
*/
|
|
313
|
+
plugins: KubbPlugin[]
|
|
314
|
+
/**
|
|
315
|
+
* Current plugin
|
|
316
|
+
*/
|
|
317
|
+
plugin: KubbPlugin
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
// null will mean clear the watcher for this key
|
|
321
|
+
export type TransformResult = string | null
|
|
322
|
+
|
|
323
|
+
export type AppMeta = { pluginManager: PluginManager }
|
|
324
|
+
|
|
325
|
+
// generic types
|
|
326
|
+
|
|
327
|
+
export type Prettify<T> =
|
|
328
|
+
& {
|
|
329
|
+
[K in keyof T]: T[K]
|
|
330
|
+
}
|
|
331
|
+
// eslint-disable-next-line @typescript-eslint/ban-types
|
|
332
|
+
& {}
|
|
333
|
+
|
|
334
|
+
/**
|
|
335
|
+
* TODO move to @kubb/types
|
|
336
|
+
* @deprecated
|
|
337
|
+
*/
|
|
338
|
+
export type PossiblePromise<T> = Promise<T> | T
|
|
339
|
+
|
|
340
|
+
type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends (k: infer I) => void ? I : never
|
|
341
|
+
type LastOf<T> = UnionToIntersection<T extends any ? () => T : never> extends () => infer R ? R : never
|
|
342
|
+
|
|
343
|
+
// TS4.0+
|
|
344
|
+
type Push<T extends any[], V> = [...T, V]
|
|
345
|
+
|
|
346
|
+
// TS4.1+
|
|
347
|
+
type TuplifyUnion<T, L = LastOf<T>, N = [T] extends [never] ? true : false> = true extends N ? [] : Push<TuplifyUnion<Exclude<T, L>>, L>
|
|
348
|
+
/**
|
|
349
|
+
* TODO move to @kubb/types
|
|
350
|
+
* @deprecated
|
|
351
|
+
*/
|
|
352
|
+
export type ObjValueTuple<T, KS extends any[] = TuplifyUnion<keyof T>, R extends any[] = []> = KS extends [infer K, ...infer KT]
|
|
353
|
+
? ObjValueTuple<T, KT, [...R, [name: K & keyof T, options: T[K & keyof T]]]>
|
|
354
|
+
: R
|
|
355
|
+
/**
|
|
356
|
+
* TODO move to @kubb/types
|
|
357
|
+
* @deprecated
|
|
358
|
+
*/
|
|
359
|
+
export type TupleToUnion<T> = T extends Array<infer ITEMS> ? ITEMS : never
|
|
360
|
+
|
|
361
|
+
/**
|
|
362
|
+
* TODO move to @kubb/types
|
|
363
|
+
* @deprecated
|
|
364
|
+
*/
|
|
365
|
+
type ArrayWithLength<T extends number, U extends any[] = []> = U['length'] extends T ? U : ArrayWithLength<T, [true, ...U]>
|
|
366
|
+
/**
|
|
367
|
+
* TODO move to @kubb/types
|
|
368
|
+
* @deprecated
|
|
369
|
+
*/
|
|
370
|
+
export type GreaterThan<T extends number, U extends number> = ArrayWithLength<U> extends [...ArrayWithLength<T>, ...infer _] ? false : true
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-unsafe-argument */
|
|
2
|
+
import { EventEmitter as NodeEventEmitter } from 'node:events'
|
|
3
|
+
|
|
4
|
+
export class EventEmitter<TEvents extends Record<string, any>> {
|
|
5
|
+
constructor() {
|
|
6
|
+
this.#emitter.setMaxListeners(100)
|
|
7
|
+
}
|
|
8
|
+
#emitter = new NodeEventEmitter()
|
|
9
|
+
|
|
10
|
+
emit<TEventName extends keyof TEvents & string>(eventName: TEventName, ...eventArg: TEvents[TEventName]): void {
|
|
11
|
+
this.#emitter.emit(eventName, ...(eventArg as []))
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
on<TEventName extends keyof TEvents & string>(eventName: TEventName, handler: (...eventArg: TEvents[TEventName]) => void): void {
|
|
15
|
+
this.#emitter.on(eventName, handler as any)
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
off<TEventName extends keyof TEvents & string>(eventName: TEventName, handler: (...eventArg: TEvents[TEventName]) => void): void {
|
|
19
|
+
this.#emitter.off(eventName, handler as any)
|
|
20
|
+
}
|
|
21
|
+
removeAll(): void {
|
|
22
|
+
this.#emitter.removeAllListeners()
|
|
23
|
+
}
|
|
24
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { camelCase, camelCaseTransformMerge } from 'change-case'
|
|
2
|
+
import { orderBy } from 'natural-orderby'
|
|
3
|
+
|
|
4
|
+
type FunctionParamsASTWithoutType = {
|
|
5
|
+
name?: string
|
|
6
|
+
type?: string
|
|
7
|
+
/**
|
|
8
|
+
* @default true
|
|
9
|
+
*/
|
|
10
|
+
required?: boolean
|
|
11
|
+
/**
|
|
12
|
+
* @default true
|
|
13
|
+
*/
|
|
14
|
+
enabled?: boolean
|
|
15
|
+
default?: string
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
type FunctionParamsASTWithType = {
|
|
19
|
+
name?: never
|
|
20
|
+
type: string
|
|
21
|
+
/**
|
|
22
|
+
* @default true
|
|
23
|
+
*/
|
|
24
|
+
required?: boolean
|
|
25
|
+
/**
|
|
26
|
+
* @default true
|
|
27
|
+
*/
|
|
28
|
+
enabled?: boolean
|
|
29
|
+
default?: string
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export type FunctionParamsAST = FunctionParamsASTWithoutType | FunctionParamsASTWithType
|
|
33
|
+
export class FunctionParams {
|
|
34
|
+
public type?: 'generics' | 'typed'
|
|
35
|
+
public items: FunctionParamsAST[] = []
|
|
36
|
+
constructor(type?: 'generics' | 'typed') {
|
|
37
|
+
this.type = type
|
|
38
|
+
|
|
39
|
+
return this
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
add(item: FunctionParamsAST | Array<FunctionParamsAST | undefined> | undefined): FunctionParams {
|
|
43
|
+
if (!item) {
|
|
44
|
+
return this
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if (Array.isArray(item)) {
|
|
48
|
+
item.filter(Boolean).forEach((it) => this.items.push(it))
|
|
49
|
+
return this
|
|
50
|
+
}
|
|
51
|
+
this.items.push(item)
|
|
52
|
+
|
|
53
|
+
return this
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
toString(): string {
|
|
57
|
+
const sortedData = orderBy(this.items.filter(Boolean), [(v) => !v.default, (v) => v.required ?? true], ['desc', 'desc'])
|
|
58
|
+
|
|
59
|
+
return sortedData
|
|
60
|
+
.filter(({ enabled = true }) => enabled)
|
|
61
|
+
.reduce((acc, { name, type, required = true, ...rest }) => {
|
|
62
|
+
if (!name) {
|
|
63
|
+
// when name is not se we will use TypeScript generics
|
|
64
|
+
acc.push(`${type}${rest.default ? ` = ${rest.default}` : ''}`)
|
|
65
|
+
|
|
66
|
+
return acc
|
|
67
|
+
}
|
|
68
|
+
// TODO check whey we still need the camelcase here
|
|
69
|
+
const parameterName = name.startsWith('{') ? name : camelCase(name, { delimiter: '', transform: camelCaseTransformMerge })
|
|
70
|
+
|
|
71
|
+
if (type) {
|
|
72
|
+
if (required) {
|
|
73
|
+
acc.push(`${parameterName}: ${type}${rest.default ? ` = ${rest.default}` : ''}`)
|
|
74
|
+
} else {
|
|
75
|
+
acc.push(`${parameterName}?: ${type}`)
|
|
76
|
+
}
|
|
77
|
+
} else {
|
|
78
|
+
acc.push(`${parameterName}`)
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return acc
|
|
82
|
+
}, [] as string[])
|
|
83
|
+
.join(', ')
|
|
84
|
+
}
|
|
85
|
+
}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import crypto from 'node:crypto'
|
|
2
|
+
import { performance } from 'node:perf_hooks'
|
|
3
|
+
|
|
4
|
+
import { EventEmitter } from './EventEmitter.ts'
|
|
5
|
+
|
|
6
|
+
export type QueueJob<T = unknown> = {
|
|
7
|
+
(...args: unknown[]): Promise<T | void>
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
type RunOptions = {
|
|
11
|
+
controller?: AbortController
|
|
12
|
+
name?: string
|
|
13
|
+
description?: string
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
type QueueItem = {
|
|
17
|
+
reject: <T>(reason?: T) => void
|
|
18
|
+
resolve: <T>(value: T | PromiseLike<T>) => void
|
|
19
|
+
job: QueueJob<unknown>
|
|
20
|
+
} & Required<RunOptions>
|
|
21
|
+
|
|
22
|
+
type Events = {
|
|
23
|
+
jobDone: [result: unknown]
|
|
24
|
+
jobFailed: [error: Error]
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export class Queue {
|
|
28
|
+
#queue: QueueItem[] = []
|
|
29
|
+
readonly eventEmitter: EventEmitter<Events> = new EventEmitter()
|
|
30
|
+
|
|
31
|
+
#workerCount = 0
|
|
32
|
+
|
|
33
|
+
#maxParallel: number
|
|
34
|
+
#debug = false
|
|
35
|
+
|
|
36
|
+
constructor(maxParallel: number, debug = false) {
|
|
37
|
+
this.#maxParallel = maxParallel
|
|
38
|
+
this.#debug = debug
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
run<T>(job: QueueJob<T>, options: RunOptions = { controller: new AbortController(), name: crypto.randomUUID(), description: '' }): Promise<T> {
|
|
42
|
+
return new Promise<T>((resolve, reject) => {
|
|
43
|
+
const item = { reject, resolve, job, name: options.name, description: options.description || options.name } as QueueItem
|
|
44
|
+
|
|
45
|
+
options.controller?.signal.addEventListener('abort', () => {
|
|
46
|
+
this.#queue = this.#queue.filter((queueItem) => queueItem.name === item.name)
|
|
47
|
+
|
|
48
|
+
reject('Aborted')
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
this.#queue.push(item)
|
|
52
|
+
this.#work()
|
|
53
|
+
})
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
runSync<T>(job: QueueJob<T>, options: RunOptions = { controller: new AbortController(), name: crypto.randomUUID(), description: '' }): void {
|
|
57
|
+
new Promise<T>((resolve, reject) => {
|
|
58
|
+
const item = { reject, resolve, job, name: options.name, description: options.description || options.name } as QueueItem
|
|
59
|
+
|
|
60
|
+
options.controller?.signal.addEventListener('abort', () => {
|
|
61
|
+
this.#queue = this.#queue.filter((queueItem) => queueItem.name === item.name)
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
this.#queue.push(item)
|
|
65
|
+
this.#work()
|
|
66
|
+
})
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
get hasJobs(): boolean {
|
|
70
|
+
return this.#workerCount > 0 || this.#queue.length > 0
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
get count(): number {
|
|
74
|
+
return this.#workerCount
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
#work(): void {
|
|
78
|
+
if (this.#workerCount >= this.#maxParallel) {
|
|
79
|
+
return
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
this.#workerCount++
|
|
83
|
+
|
|
84
|
+
let entry: QueueItem | undefined
|
|
85
|
+
while ((entry = this.#queue.shift())) {
|
|
86
|
+
const { reject, resolve, job, name, description } = entry
|
|
87
|
+
|
|
88
|
+
if (this.#debug) {
|
|
89
|
+
performance.mark(name + '_start')
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
job()
|
|
93
|
+
.then((result) => {
|
|
94
|
+
this.eventEmitter.emit('jobDone', result)
|
|
95
|
+
|
|
96
|
+
resolve(result)
|
|
97
|
+
|
|
98
|
+
if (this.#debug) {
|
|
99
|
+
performance.mark(name + '_stop')
|
|
100
|
+
performance.measure(description, name + '_start', name + '_stop')
|
|
101
|
+
}
|
|
102
|
+
})
|
|
103
|
+
.catch((err) => {
|
|
104
|
+
this.eventEmitter.emit('jobFailed', err as Error)
|
|
105
|
+
reject(err)
|
|
106
|
+
})
|
|
107
|
+
}
|
|
108
|
+
this.#workerCount--
|
|
109
|
+
}
|
|
110
|
+
}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import dirTree from 'directory-tree'
|
|
2
|
+
|
|
3
|
+
import { FileManager } from '../FileManager.ts'
|
|
4
|
+
|
|
5
|
+
import type { DirectoryTree, DirectoryTreeOptions } from 'directory-tree'
|
|
6
|
+
|
|
7
|
+
export type TreeNodeOptions = DirectoryTreeOptions
|
|
8
|
+
|
|
9
|
+
export class TreeNode<T = unknown> {
|
|
10
|
+
public data: T
|
|
11
|
+
|
|
12
|
+
public parent?: TreeNode<T>
|
|
13
|
+
|
|
14
|
+
public children: Array<TreeNode<T>> = []
|
|
15
|
+
|
|
16
|
+
constructor(data: T, parent?: TreeNode<T>) {
|
|
17
|
+
this.data = data
|
|
18
|
+
this.parent = parent
|
|
19
|
+
return this
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
addChild(data: T): TreeNode<T> {
|
|
23
|
+
const child = new TreeNode(data, this)
|
|
24
|
+
if (!this.children) {
|
|
25
|
+
this.children = []
|
|
26
|
+
}
|
|
27
|
+
this.children.push(child)
|
|
28
|
+
return child
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
find(data?: T): TreeNode<T> | null {
|
|
32
|
+
if (!data) {
|
|
33
|
+
return null
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (data === this.data) {
|
|
37
|
+
return this
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if (this.children?.length) {
|
|
41
|
+
for (let i = 0, { length } = this.children, target: TreeNode<T> | null = null; i < length; i++) {
|
|
42
|
+
target = this.children[i]!.find(data)
|
|
43
|
+
if (target) {
|
|
44
|
+
return target
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return null
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
get leaves(): TreeNode<T>[] {
|
|
53
|
+
if (!this.children || this.children.length === 0) {
|
|
54
|
+
// this is a leaf
|
|
55
|
+
return [this]
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// if not a leaf, return all children's leaves recursively
|
|
59
|
+
const leaves: TreeNode<T>[] = []
|
|
60
|
+
if (this.children) {
|
|
61
|
+
for (let i = 0, { length } = this.children; i < length; i++) {
|
|
62
|
+
// eslint-disable-next-line prefer-spread
|
|
63
|
+
leaves.push.apply(leaves, this.children[i]!.leaves)
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
return leaves
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
get root(): TreeNode<T> {
|
|
70
|
+
if (!this.parent) {
|
|
71
|
+
return this
|
|
72
|
+
}
|
|
73
|
+
return this.parent.root
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
forEach(callback: (treeNode: TreeNode<T>) => void): this {
|
|
77
|
+
if (typeof callback !== 'function') {
|
|
78
|
+
throw new TypeError('forEach() callback must be a function')
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// run this node through function
|
|
82
|
+
callback(this)
|
|
83
|
+
|
|
84
|
+
// do the same for all children
|
|
85
|
+
if (this.children) {
|
|
86
|
+
for (let i = 0, { length } = this.children; i < length; i++) {
|
|
87
|
+
this.children[i]!.forEach(callback)
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return this
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
public static build<T = unknown>(path: string, options: TreeNodeOptions = {}): TreeNode<T> | null {
|
|
95
|
+
try {
|
|
96
|
+
const exclude = Array.isArray(options.exclude) ? options.exclude : [options.exclude].filter(Boolean)
|
|
97
|
+
const filteredTree = dirTree(path, { extensions: options.extensions, exclude: [/node_modules/, ...exclude] })
|
|
98
|
+
|
|
99
|
+
if (!filteredTree) {
|
|
100
|
+
return null
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const treeNode = new TreeNode({ name: filteredTree.name, path: filteredTree.path, type: filteredTree.type || FileManager.getMode(filteredTree.path) })
|
|
104
|
+
|
|
105
|
+
const recurse = (node: typeof treeNode, item: DirectoryTree) => {
|
|
106
|
+
const subNode = node.addChild({ name: item.name, path: item.path, type: item.type || FileManager.getMode(item.path) })
|
|
107
|
+
|
|
108
|
+
if (item.children?.length) {
|
|
109
|
+
item.children?.forEach((child) => {
|
|
110
|
+
recurse(subNode, child)
|
|
111
|
+
})
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
filteredTree.children?.forEach((child) => recurse(treeNode, child))
|
|
116
|
+
|
|
117
|
+
return treeNode as TreeNode<T>
|
|
118
|
+
} catch (e) {
|
|
119
|
+
throw new Error('Something went wrong with creating index files with the TreehNode class', { cause: e })
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|