@kubb/core 1.15.0-canary.20231112T135011 → 2.0.0-alpha.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. package/README.md +1 -1
  2. package/dist/index.cjs +1253 -1088
  3. package/dist/index.cjs.map +1 -1
  4. package/dist/index.d.cts +396 -411
  5. package/dist/index.d.ts +396 -411
  6. package/dist/index.js +1194 -1018
  7. package/dist/index.js.map +1 -1
  8. package/dist/utils.cjs +1272 -0
  9. package/dist/utils.cjs.map +1 -0
  10. package/dist/utils.d.cts +239 -0
  11. package/dist/utils.d.ts +239 -0
  12. package/dist/utils.js +1219 -0
  13. package/dist/utils.js.map +1 -0
  14. package/globals.d.ts +33 -16
  15. package/package.json +21 -14
  16. package/src/BarrelManager.ts +123 -0
  17. package/src/FileManager.ts +524 -0
  18. package/src/Generator.ts +34 -0
  19. package/src/PackageManager.ts +178 -0
  20. package/src/PluginManager.ts +629 -0
  21. package/src/PromiseManager.ts +51 -0
  22. package/src/SchemaGenerator.ts +8 -0
  23. package/src/build.ts +207 -0
  24. package/src/config.ts +22 -0
  25. package/src/errors.ts +12 -0
  26. package/src/index.ts +28 -0
  27. package/src/plugin.ts +80 -0
  28. package/src/types.ts +353 -0
  29. package/src/utils/EventEmitter.ts +24 -0
  30. package/src/utils/FunctionParams.ts +85 -0
  31. package/src/utils/Queue.ts +110 -0
  32. package/src/utils/TreeNode.ts +122 -0
  33. package/src/utils/URLPath.ts +133 -0
  34. package/src/utils/cache.ts +35 -0
  35. package/src/utils/clean.ts +5 -0
  36. package/src/utils/executeStrategies.ts +83 -0
  37. package/src/utils/index.ts +19 -0
  38. package/src/utils/logger.ts +76 -0
  39. package/src/utils/promise.ts +13 -0
  40. package/src/utils/randomColour.ts +39 -0
  41. package/src/utils/read.ts +68 -0
  42. package/src/utils/renderTemplate.ts +31 -0
  43. package/src/utils/throttle.ts +30 -0
  44. package/src/utils/timeout.ts +7 -0
  45. package/src/utils/transformers/combineCodes.ts +3 -0
  46. package/src/utils/transformers/createJSDocBlockText.ts +15 -0
  47. package/src/utils/transformers/escape.ts +31 -0
  48. package/src/utils/transformers/indent.ts +3 -0
  49. package/src/utils/transformers/index.ts +22 -0
  50. package/src/utils/transformers/nameSorter.ts +9 -0
  51. package/src/utils/transformers/searchAndReplace.ts +25 -0
  52. package/src/utils/transformers/transformReservedWord.ts +97 -0
  53. package/src/utils/transformers/trim.ts +3 -0
  54. package/src/utils/uniqueName.ts +20 -0
  55. package/src/utils/write.ts +63 -0
@@ -0,0 +1,524 @@
1
+ /* eslint-disable @typescript-eslint/no-namespace */
2
+ import crypto from 'node:crypto'
3
+ import { extname } from 'node:path'
4
+
5
+ import { print } from '@kubb/parser'
6
+ import * as factory from '@kubb/parser/factory'
7
+
8
+ import isEqual from 'lodash.isequal'
9
+ import { orderBy } from 'natural-orderby'
10
+
11
+ import { read } from './utils/read.ts'
12
+ import { timeout } from './utils/timeout.ts'
13
+ import { transformers } from './utils/transformers/index.ts'
14
+ import { write } from './utils/write.ts'
15
+ import { BarrelManager } from './BarrelManager.ts'
16
+
17
+ import type { GreaterThan } from '@kubb/types'
18
+ import type { BarrelManagerOptions } from './BarrelManager.ts'
19
+ import type { KubbPlugin } from './types.ts'
20
+ import type { Queue, QueueJob } from './utils/Queue.ts'
21
+
22
+ type BasePath<T extends string = string> = `${T}/`
23
+
24
+ export namespace KubbFile {
25
+ export type Import = {
26
+ /**
27
+ * Import name to be used
28
+ * @example ["useState"]
29
+ * @example "React"
30
+ */
31
+ name: string | Array<string>
32
+ /**
33
+ * Path for the import
34
+ * @xample '@kubb/core'
35
+ */
36
+ path: string
37
+ /**
38
+ * Add `type` prefix to the import, this will result in: `import type { Type } from './path'`.
39
+ */
40
+ isTypeOnly?: boolean
41
+ }
42
+
43
+ export type Export = {
44
+ /**
45
+ * Export name to be used.
46
+ * @example ["useState"]
47
+ * @example "React"
48
+ */
49
+ name?: string | Array<string>
50
+ /**
51
+ * Path for the import.
52
+ * @xample '@kubb/core'
53
+ */
54
+ path: string
55
+ /**
56
+ * Add `type` prefix to the export, this will result in: `export type { Type } from './path'`.
57
+ */
58
+ isTypeOnly?: boolean
59
+ /**
60
+ * Make it possible to override the name, this will result in: `export * as aliasName from './path'`.
61
+ */
62
+ asAlias?: boolean
63
+ }
64
+
65
+ export declare const dataTagSymbol: unique symbol
66
+ export type DataTag<Type, Value> = Type & {
67
+ [dataTagSymbol]: Value
68
+ }
69
+
70
+ export type UUID = string
71
+ export type Source = string
72
+
73
+ export type Extname = '.ts' | '.js' | '.tsx' | '.json' | `.${string}`
74
+
75
+ export type Mode = 'file' | 'directory'
76
+
77
+ /**
78
+ * Name to be used to dynamicly create the baseName(based on input.path)
79
+ * Based on UNIX basename
80
+ * @link https://nodejs.org/api/path.html#pathbasenamepath-suffix
81
+ */
82
+ export type BaseName = `${string}${Extname}`
83
+
84
+ /**
85
+ * Path will be full qualified path to a specified file
86
+ */
87
+ export type Path = string
88
+
89
+ export type AdvancedPath<T extends BaseName = BaseName> = `${BasePath}${T}`
90
+
91
+ export type OptionalPath = Path | undefined | null
92
+
93
+ export type FileMetaBase = {
94
+ pluginKey?: KubbPlugin['key']
95
+ }
96
+
97
+ export type File<
98
+ TMeta extends FileMetaBase = FileMetaBase,
99
+ TBaseName extends BaseName = BaseName,
100
+ > = {
101
+ /**
102
+ * Unique identifier to reuse later
103
+ * @default crypto.randomUUID()
104
+ */
105
+ id?: string
106
+ /**
107
+ * Name to be used to dynamicly create the baseName(based on input.path)
108
+ * Based on UNIX basename
109
+ * @link https://nodejs.org/api/path.html#pathbasenamepath-suffix
110
+ */
111
+ baseName: TBaseName
112
+ /**
113
+ * Path will be full qualified path to a specified file
114
+ */
115
+ path: AdvancedPath<TBaseName> | Path
116
+ source: Source
117
+ imports?: Import[]
118
+ exports?: Export[]
119
+ /**
120
+ * This will call fileManager.add instead of fileManager.addOrAppend, adding the source when the files already exists
121
+ * This will also ignore the combinefiles utils
122
+ * @default `false`
123
+ */
124
+ override?: boolean
125
+ /**
126
+ * Use extra meta, this is getting used to generate the barrel/index files.
127
+ */
128
+ meta?: TMeta
129
+ /**
130
+ * This will override `process.env[key]` inside the `source`, see `getFileSource`.
131
+ */
132
+ env?: NodeJS.ProcessEnv
133
+ /**
134
+ * @deprecated
135
+ */
136
+ validate?: boolean
137
+ }
138
+
139
+ export type ResolvedFile<
140
+ TMeta extends FileMetaBase = FileMetaBase,
141
+ TBaseName extends BaseName = BaseName,
142
+ > = KubbFile.File<TMeta, TBaseName> & {
143
+ /**
144
+ * @default crypto.randomUUID()
145
+ */
146
+ id: UUID
147
+ }
148
+ }
149
+
150
+ type CacheItem = KubbFile.ResolvedFile & {
151
+ cancel?: () => void
152
+ }
153
+
154
+ type AddResult<T extends Array<KubbFile.File>> = Promise<
155
+ Awaited<GreaterThan<T['length'], 1> extends true ? Promise<KubbFile.ResolvedFile[]> : Promise<KubbFile.ResolvedFile>>
156
+ >
157
+
158
+ type AddIndexesProps = {
159
+ root: KubbFile.Path
160
+ extName?: KubbFile.Extname
161
+ options?: BarrelManagerOptions
162
+ meta?: KubbFile.File['meta']
163
+ }
164
+
165
+ type Options = {
166
+ queue?: Queue
167
+ task?: QueueJob<KubbFile.ResolvedFile>
168
+ /**
169
+ * Timeout between writes
170
+ */
171
+ timeout?: number
172
+ }
173
+
174
+ export class FileManager {
175
+ #cache: Map<KubbFile.Path, CacheItem[]> = new Map()
176
+
177
+ #task?: QueueJob<KubbFile.ResolvedFile>
178
+ #isWriting = false
179
+ /**
180
+ * Timeout between writes
181
+ */
182
+ #timeout: number = 0
183
+ #queue?: Queue
184
+
185
+ constructor(options?: Options) {
186
+ if (options) {
187
+ this.#task = options.task
188
+ this.#queue = options.queue
189
+ this.#timeout = options.timeout || 0
190
+ }
191
+
192
+ return this
193
+ }
194
+
195
+ get files(): Array<KubbFile.File> {
196
+ const files: Array<KubbFile.File> = []
197
+ this.#cache.forEach((item) => {
198
+ files.push(...item.flat(1))
199
+ })
200
+
201
+ return files
202
+ }
203
+ get isExecuting(): boolean {
204
+ return this.#queue?.hasJobs ?? this.#isWriting ?? false
205
+ }
206
+
207
+ #validate(file: KubbFile.File): void {
208
+ if (!file.validate) {
209
+ return
210
+ }
211
+
212
+ if (!file.path.toLowerCase().endsWith(file.baseName.toLowerCase())) {
213
+ throw new Error(`${file.path} should end with the baseName ${file.baseName}`)
214
+ }
215
+ }
216
+
217
+ async add<T extends Array<KubbFile.File> = Array<KubbFile.File>>(
218
+ ...files: T
219
+ ): AddResult<T> {
220
+ const promises = files.map((file) => {
221
+ this.#validate(file)
222
+
223
+ if (file.override) {
224
+ return this.#add(file)
225
+ }
226
+
227
+ return this.#addOrAppend(file)
228
+ })
229
+
230
+ const resolvedFiles = await Promise.all(promises)
231
+
232
+ if (files.length > 1) {
233
+ return resolvedFiles as unknown as AddResult<T>
234
+ }
235
+
236
+ return resolvedFiles[0] as unknown as AddResult<T>
237
+ }
238
+
239
+ async #add(file: KubbFile.File): Promise<KubbFile.ResolvedFile> {
240
+ const controller = new AbortController()
241
+ const resolvedFile: KubbFile.ResolvedFile = { id: crypto.randomUUID(), ...file }
242
+
243
+ this.#cache.set(resolvedFile.path, [{ cancel: () => controller.abort(), ...resolvedFile }])
244
+
245
+ if (this.#queue) {
246
+ await this.#queue.run(
247
+ async () => {
248
+ return this.#task?.(resolvedFile)
249
+ },
250
+ { controller },
251
+ )
252
+ }
253
+
254
+ return resolvedFile
255
+ }
256
+
257
+ async #addOrAppend(file: KubbFile.File): Promise<KubbFile.ResolvedFile> {
258
+ const previousCaches = this.#cache.get(file.path)
259
+ const previousCache = previousCaches ? previousCaches.at(previousCaches.length - 1) : undefined
260
+
261
+ if (previousCache) {
262
+ this.#cache.delete(previousCache.path)
263
+
264
+ return this.#add({
265
+ ...file,
266
+ source: previousCache.source && file.source ? `${previousCache.source}\n${file.source}` : '',
267
+ imports: [...(previousCache.imports || []), ...(file.imports || [])],
268
+ exports: [...(previousCache.exports || []), ...(file.exports || [])],
269
+ env: { ...(previousCache.env || {}), ...(file.env || {}) },
270
+ })
271
+ }
272
+ return this.#add(file)
273
+ }
274
+
275
+ async addIndexes({ root, extName = '.ts', meta, options = {} }: AddIndexesProps): Promise<Array<KubbFile.File> | undefined> {
276
+ const barrelManager = new BarrelManager(options)
277
+
278
+ const files = barrelManager.getIndexes(root, extName)
279
+
280
+ if (!files) {
281
+ return undefined
282
+ }
283
+
284
+ return await Promise.all(
285
+ files.map((file) => {
286
+ return this.#addOrAppend({
287
+ ...file,
288
+ meta: meta ? meta : file.meta,
289
+ })
290
+ }),
291
+ )
292
+ }
293
+
294
+ getCacheByUUID(UUID: KubbFile.UUID): KubbFile.File | undefined {
295
+ let cache: KubbFile.File | undefined
296
+
297
+ this.#cache.forEach((files) => {
298
+ cache = files.find((item) => item.id === UUID)
299
+ })
300
+ return cache
301
+ }
302
+
303
+ get(path: KubbFile.Path): Array<KubbFile.File> | undefined {
304
+ return this.#cache.get(path)
305
+ }
306
+
307
+ remove(path: KubbFile.Path): void {
308
+ const cacheItem = this.get(path)
309
+ if (!cacheItem) {
310
+ return
311
+ }
312
+
313
+ this.#cache.delete(path)
314
+ }
315
+
316
+ async write(...params: Parameters<typeof write>): Promise<string | undefined> {
317
+ if (!this.#isWriting) {
318
+ this.#isWriting = true
319
+
320
+ const text = await write(...params)
321
+
322
+ this.#isWriting = false
323
+ return text
324
+ }
325
+
326
+ await timeout(this.#timeout)
327
+
328
+ return this.write(...params)
329
+ }
330
+
331
+ async read(...params: Parameters<typeof read>): Promise<string> {
332
+ return read(...params)
333
+ }
334
+
335
+ // statics
336
+
337
+ static getSource<TMeta extends KubbFile.FileMetaBase = KubbFile.FileMetaBase>(file: KubbFile.File<TMeta>): string {
338
+ if (!FileManager.isExtensionAllowed(file.baseName)) {
339
+ return file.source
340
+ }
341
+
342
+ const exports = file.exports ? combineExports(file.exports) : []
343
+ const imports = file.imports ? combineImports(file.imports, exports, file.source) : []
344
+
345
+ const importNodes = imports.map((item) => factory.createImportDeclaration({ name: item.name, path: item.path, isTypeOnly: item.isTypeOnly }))
346
+ const exportNodes = exports.map((item) =>
347
+ factory.createExportDeclaration({ name: item.name, path: item.path, isTypeOnly: item.isTypeOnly, asAlias: item.asAlias })
348
+ )
349
+
350
+ return [print([...importNodes, ...exportNodes]), getEnvSource(file.source, file.env)].join('\n')
351
+ }
352
+ static combineFiles<TMeta extends KubbFile.FileMetaBase = KubbFile.FileMetaBase>(files: Array<KubbFile.File<TMeta> | null>): Array<KubbFile.File<TMeta>> {
353
+ return files.filter(Boolean).reduce((acc, file: KubbFile.File<TMeta>) => {
354
+ const prevIndex = acc.findIndex((item) => item.path === file.path)
355
+
356
+ if (prevIndex === -1) {
357
+ return [...acc, file]
358
+ }
359
+
360
+ const prev = acc[prevIndex]
361
+
362
+ if (prev && file.override) {
363
+ acc[prevIndex] = {
364
+ imports: [],
365
+ exports: [],
366
+ ...file,
367
+ }
368
+ return acc
369
+ }
370
+
371
+ if (prev) {
372
+ acc[prevIndex] = {
373
+ ...file,
374
+ source: prev.source && file.source ? `${prev.source}\n${file.source}` : '',
375
+ imports: [...(prev.imports || []), ...(file.imports || [])],
376
+ exports: [...(prev.exports || []), ...(file.exports || [])],
377
+ env: { ...(prev.env || {}), ...(file.env || {}) },
378
+ }
379
+ }
380
+
381
+ return acc
382
+ }, [] as Array<KubbFile.File<TMeta>>)
383
+ }
384
+ static getMode(path: string | undefined | null): KubbFile.Mode {
385
+ if (!path) {
386
+ return 'directory'
387
+ }
388
+ return extname(path) ? 'file' : 'directory'
389
+ }
390
+
391
+ static get extensions(): Array<KubbFile.Extname> {
392
+ return ['.js', '.ts', '.tsx']
393
+ }
394
+
395
+ static isExtensionAllowed(baseName: string): boolean {
396
+ return FileManager.extensions.some((extension) => baseName.endsWith(extension))
397
+ }
398
+ }
399
+
400
+ export function combineExports(exports: Array<KubbFile.Export>): Array<KubbFile.Export> {
401
+ const combinedExports = orderBy(exports, [(v) => !v.isTypeOnly], ['asc']).reduce((prev, curr) => {
402
+ const name = curr.name
403
+ const prevByPath = prev.findLast((imp) => imp.path === curr.path)
404
+ const prevByPathAndIsTypeOnly = prev.findLast((imp) => imp.path === curr.path && isEqual(imp.name, name) && imp.isTypeOnly)
405
+
406
+ if (prevByPathAndIsTypeOnly) {
407
+ // we already have an export that has the same path but uses `isTypeOnly` (export type ...)
408
+ return prev
409
+ }
410
+
411
+ const uniquePrev = prev.findLast(
412
+ (imp) => imp.path === curr.path && isEqual(imp.name, name) && imp.isTypeOnly === curr.isTypeOnly && imp.asAlias === curr.asAlias,
413
+ )
414
+
415
+ if (uniquePrev || (Array.isArray(name) && !name.length) || (prevByPath?.asAlias && !curr.asAlias)) {
416
+ return prev
417
+ }
418
+
419
+ if (!prevByPath) {
420
+ return [
421
+ ...prev,
422
+ {
423
+ ...curr,
424
+ name: Array.isArray(name) ? [...new Set(name)] : name,
425
+ },
426
+ ]
427
+ }
428
+
429
+ if (prevByPath && Array.isArray(prevByPath.name) && Array.isArray(curr.name) && prevByPath.isTypeOnly === curr.isTypeOnly) {
430
+ prevByPath.name = [...new Set([...prevByPath.name, ...curr.name])]
431
+
432
+ return prev
433
+ }
434
+
435
+ return [...prev, curr]
436
+ }, [] as Array<KubbFile.Export>)
437
+
438
+ return orderBy(combinedExports, [(v) => !v.isTypeOnly, (v) => v.asAlias], ['desc', 'desc'])
439
+ }
440
+
441
+ export function combineImports(imports: Array<KubbFile.Import>, exports: Array<KubbFile.Export>, source?: string): Array<KubbFile.Import> {
442
+ const combinedImports = orderBy(imports, [(v) => !v.isTypeOnly], ['asc']).reduce((prev, curr) => {
443
+ let name = Array.isArray(curr.name) ? [...new Set(curr.name)] : curr.name
444
+
445
+ const hasImportInSource = (importName: string) => {
446
+ if (!source) {
447
+ return true
448
+ }
449
+
450
+ const checker = (name?: string) => name && !!source.includes(name)
451
+ return checker(importName) || exports.some(({ name }) => (Array.isArray(name) ? name.some(checker) : checker(name)))
452
+ }
453
+
454
+ if (Array.isArray(name)) {
455
+ name = name.filter((item) => hasImportInSource(item))
456
+ }
457
+
458
+ const prevByPath = prev.findLast((imp) => imp.path === curr.path && imp.isTypeOnly === curr.isTypeOnly)
459
+ const uniquePrev = prev.findLast((imp) => imp.path === curr.path && isEqual(imp.name, name) && imp.isTypeOnly === curr.isTypeOnly)
460
+ const prevByPathNameAndIsTypeOnly = prev.findLast((imp) => imp.path === curr.path && isEqual(imp.name, name) && imp.isTypeOnly)
461
+
462
+ if (prevByPathNameAndIsTypeOnly) {
463
+ // we already have an export that has the same path but uses `isTypeOnly` (import type ...)
464
+ return prev
465
+ }
466
+
467
+ if (uniquePrev || (Array.isArray(name) && !name.length)) {
468
+ return prev
469
+ }
470
+
471
+ if (!prevByPath) {
472
+ return [
473
+ ...prev,
474
+ {
475
+ ...curr,
476
+ name,
477
+ },
478
+ ]
479
+ }
480
+
481
+ if (prevByPath && Array.isArray(prevByPath.name) && Array.isArray(name) && prevByPath.isTypeOnly === curr.isTypeOnly) {
482
+ prevByPath.name = [...new Set([...prevByPath.name, ...name])]
483
+
484
+ return prev
485
+ }
486
+
487
+ if (!Array.isArray(name) && name && !hasImportInSource(name)) {
488
+ return prev
489
+ }
490
+
491
+ return [...prev, curr]
492
+ }, [] as Array<KubbFile.Import>)
493
+
494
+ return orderBy(combinedImports, [(v) => !v.isTypeOnly], ['desc'])
495
+ }
496
+
497
+ function getEnvSource(source: string, env: NodeJS.ProcessEnv | undefined): string {
498
+ if (!env) {
499
+ return source
500
+ }
501
+
502
+ const keys = Object.keys(env)
503
+
504
+ if (!keys.length) {
505
+ return source
506
+ }
507
+
508
+ return keys.reduce((prev, key: string) => {
509
+ const environmentValue = env[key]
510
+ const replaceBy = environmentValue ? `'${environmentValue.replaceAll('"', '')?.replaceAll("'", '')}'` : 'undefined'
511
+
512
+ if (key.toUpperCase() !== key) {
513
+ throw new TypeError(`Environment should be in upperCase for ${key}`)
514
+ }
515
+
516
+ if (typeof replaceBy === 'string') {
517
+ prev = transformers.searchAndReplace({ text: prev.replaceAll(`process.env.${key}`, replaceBy), replaceBy, prefix: 'process.env', key })
518
+ // removes `declare const ...`
519
+ prev = transformers.searchAndReplace({ text: prev.replaceAll(new RegExp(`(declare const).*\n`, 'ig'), ''), replaceBy, key })
520
+ }
521
+
522
+ return prev
523
+ }, source)
524
+ }
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Abstract class that contains the building blocks for plugins to create their own Generator
3
+ * @link idea based on https://github.com/colinhacks/zod/blob/master/src/types.ts#L137
4
+ */
5
+ export abstract class Generator<TOptions = unknown, TContext = unknown> {
6
+ #options: TOptions = {} as TOptions
7
+ #context: TContext = {} as TContext
8
+
9
+ constructor(options?: TOptions, context?: TContext) {
10
+ if (context) {
11
+ this.#context = context
12
+ }
13
+
14
+ if (options) {
15
+ this.#options = options
16
+ }
17
+
18
+ return this
19
+ }
20
+
21
+ get options(): TOptions {
22
+ return this.#options
23
+ }
24
+
25
+ get context(): TContext {
26
+ return this.#context
27
+ }
28
+
29
+ set options(options: TOptions) {
30
+ this.#options = { ...this.#options, ...options }
31
+ }
32
+
33
+ abstract build(...params: unknown[]): unknown
34
+ }