@kubb/core 2.0.0-alpha.1 → 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.
- package/README.md +1 -1
- package/dist/index.cjs +138 -111
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +266 -223
- package/dist/index.d.ts +266 -223
- package/dist/index.js +138 -109
- package/dist/index.js.map +1 -1
- package/dist/utils.cjs +25 -13
- package/dist/utils.cjs.map +1 -1
- package/dist/utils.d.cts +7 -3
- package/dist/utils.d.ts +7 -3
- package/dist/utils.js +25 -13
- package/dist/utils.js.map +1 -1
- package/package.json +13 -11
- package/src/BarrelManager.ts +123 -0
- package/src/FileManager.ts +524 -0
- package/src/Generator.ts +34 -0
- package/src/PackageManager.ts +178 -0
- package/src/PluginManager.ts +629 -0
- package/src/PromiseManager.ts +51 -0
- package/src/SchemaGenerator.ts +8 -0
- package/src/build.ts +207 -0
- package/src/config.ts +22 -0
- package/src/errors.ts +12 -0
- package/src/index.ts +28 -0
- package/src/plugin.ts +80 -0
- package/src/types.ts +353 -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 +133 -0
- package/src/utils/cache.ts +35 -0
- package/src/utils/clean.ts +5 -0
- package/src/utils/executeStrategies.ts +83 -0
- package/src/utils/index.ts +19 -0
- package/src/utils/logger.ts +76 -0
- package/src/utils/promise.ts +13 -0
- package/src/utils/randomColour.ts +39 -0
- package/src/utils/read.ts +68 -0
- package/src/utils/renderTemplate.ts +31 -0
- package/src/utils/throttle.ts +30 -0
- package/src/utils/timeout.ts +7 -0
- package/src/utils/transformers/combineCodes.ts +3 -0
- package/src/utils/transformers/createJSDocBlockText.ts +15 -0
- package/src/utils/transformers/escape.ts +31 -0
- package/src/utils/transformers/indent.ts +3 -0
- package/src/utils/transformers/index.ts +22 -0
- package/src/utils/transformers/nameSorter.ts +9 -0
- package/src/utils/transformers/searchAndReplace.ts +25 -0
- package/src/utils/transformers/transformReservedWord.ts +97 -0
- package/src/utils/transformers/trim.ts +3 -0
- package/src/utils/uniqueName.ts +20 -0
- package/src/utils/write.ts +63 -0
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import path from 'path'
|
|
2
|
+
|
|
3
|
+
import { TreeNode } from './utils/TreeNode.ts'
|
|
4
|
+
|
|
5
|
+
import type { DirectoryTreeOptions } from 'directory-tree'
|
|
6
|
+
import type { KubbFile } from './FileManager.ts'
|
|
7
|
+
|
|
8
|
+
type BarrelData = { type: KubbFile.Mode; path: KubbFile.Path; name: string }
|
|
9
|
+
|
|
10
|
+
export type BarrelManagerOptions = {
|
|
11
|
+
treeNode?: DirectoryTreeOptions
|
|
12
|
+
isTypeOnly?: boolean
|
|
13
|
+
filter?: (file: KubbFile.File) => boolean
|
|
14
|
+
map?: (file: KubbFile.File) => KubbFile.File
|
|
15
|
+
includeExt?: boolean
|
|
16
|
+
output?: string
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export class BarrelManager {
|
|
20
|
+
#options: BarrelManagerOptions = {}
|
|
21
|
+
|
|
22
|
+
constructor(options: BarrelManagerOptions = {}) {
|
|
23
|
+
this.#options = options
|
|
24
|
+
|
|
25
|
+
return this
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
getIndexes(
|
|
29
|
+
root: string,
|
|
30
|
+
extName?: KubbFile.Extname,
|
|
31
|
+
): Array<KubbFile.File> | null {
|
|
32
|
+
const { treeNode = {}, isTypeOnly, filter, map, output, includeExt } = this.#options
|
|
33
|
+
|
|
34
|
+
const extMapper: Record<KubbFile.Extname, DirectoryTreeOptions> = {
|
|
35
|
+
'.ts': {
|
|
36
|
+
extensions: /\.ts/,
|
|
37
|
+
exclude: [/schemas/, /json/],
|
|
38
|
+
},
|
|
39
|
+
'.json': {
|
|
40
|
+
extensions: /\.json/,
|
|
41
|
+
exclude: [],
|
|
42
|
+
},
|
|
43
|
+
}
|
|
44
|
+
const tree = TreeNode.build<BarrelData>(root, { ...(extMapper[extName as keyof typeof extMapper] || {}), ...treeNode })
|
|
45
|
+
|
|
46
|
+
if (!tree) {
|
|
47
|
+
return null
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const fileReducer = (files: Array<KubbFile.File>, currentTree: typeof tree) => {
|
|
51
|
+
if (!currentTree.children) {
|
|
52
|
+
return []
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (currentTree.children?.length > 1) {
|
|
56
|
+
const indexPath: KubbFile.Path = path.resolve(currentTree.data.path, 'index.ts')
|
|
57
|
+
const exports: KubbFile.Export[] = currentTree.children
|
|
58
|
+
.filter(Boolean)
|
|
59
|
+
.map((file) => {
|
|
60
|
+
const importPath: string = file.data.type === 'directory' ? `./${file.data.name}/index` : `./${file.data.name.replace(/\.[^.]*$/, '')}`
|
|
61
|
+
|
|
62
|
+
if (importPath.includes('index') && file.data.type === 'file') {
|
|
63
|
+
return undefined
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return {
|
|
67
|
+
path: includeExt ? `${importPath}${extName}` : importPath,
|
|
68
|
+
isTypeOnly,
|
|
69
|
+
} as KubbFile.Export
|
|
70
|
+
})
|
|
71
|
+
.filter(Boolean)
|
|
72
|
+
|
|
73
|
+
files.push({
|
|
74
|
+
path: indexPath,
|
|
75
|
+
baseName: 'index.ts',
|
|
76
|
+
source: '',
|
|
77
|
+
exports: output
|
|
78
|
+
? exports?.filter((item) => {
|
|
79
|
+
return item.path.endsWith(output.replace(/\.[^.]*$/, ''))
|
|
80
|
+
})
|
|
81
|
+
: exports,
|
|
82
|
+
})
|
|
83
|
+
} else {
|
|
84
|
+
currentTree.children?.forEach((child) => {
|
|
85
|
+
const indexPath = path.resolve(currentTree.data.path, 'index.ts')
|
|
86
|
+
const importPath = child.data.type === 'directory' ? `./${child.data.name}/index` : `./${child.data.name.replace(/\.[^.]*$/, '')}`
|
|
87
|
+
|
|
88
|
+
const exports = [
|
|
89
|
+
{
|
|
90
|
+
path: includeExt
|
|
91
|
+
? `${importPath}${extName}`
|
|
92
|
+
: importPath,
|
|
93
|
+
isTypeOnly,
|
|
94
|
+
},
|
|
95
|
+
]
|
|
96
|
+
|
|
97
|
+
files.push({
|
|
98
|
+
path: indexPath,
|
|
99
|
+
baseName: 'index.ts',
|
|
100
|
+
source: '',
|
|
101
|
+
exports: output
|
|
102
|
+
? exports?.filter((item) => {
|
|
103
|
+
return item.path.endsWith(output.replace(/\.[^.]*$/, ''))
|
|
104
|
+
})
|
|
105
|
+
: exports,
|
|
106
|
+
})
|
|
107
|
+
})
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
currentTree.children.forEach((childItem) => {
|
|
111
|
+
fileReducer(files, childItem)
|
|
112
|
+
})
|
|
113
|
+
|
|
114
|
+
return files
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const files = fileReducer([], tree).reverse()
|
|
118
|
+
|
|
119
|
+
const filteredFiles = filter ? files.filter(filter) : files
|
|
120
|
+
|
|
121
|
+
return map ? filteredFiles.map(map) : filteredFiles
|
|
122
|
+
}
|
|
123
|
+
}
|
|
@@ -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
|
+
}
|
package/src/Generator.ts
ADDED
|
@@ -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
|
+
}
|