@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
|
@@ -0,0 +1,482 @@
|
|
|
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 { BarrelManagerOptions } from './BarrelManager.ts'
|
|
18
|
+
import type { GreaterThan, KubbPlugin } from './types.ts'
|
|
19
|
+
import type { Queue, QueueJob } from './utils/Queue.ts'
|
|
20
|
+
|
|
21
|
+
type BasePath<T extends string = string> = `${T}/`
|
|
22
|
+
|
|
23
|
+
export namespace KubbFile {
|
|
24
|
+
export type Import = {
|
|
25
|
+
name: string | Array<string>
|
|
26
|
+
path: string
|
|
27
|
+
isTypeOnly?: boolean
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export type Export = {
|
|
31
|
+
name?: string | Array<string>
|
|
32
|
+
path: string
|
|
33
|
+
isTypeOnly?: boolean
|
|
34
|
+
asAlias?: boolean
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export declare const dataTagSymbol: unique symbol
|
|
38
|
+
export type DataTag<Type, Value> = Type & {
|
|
39
|
+
[dataTagSymbol]: Value
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export type UUID = string
|
|
43
|
+
export type Source = string
|
|
44
|
+
|
|
45
|
+
export type Extname = '.ts' | '.js' | '.tsx' | '.json' | `.${string}`
|
|
46
|
+
|
|
47
|
+
export type Mode = 'file' | 'directory'
|
|
48
|
+
|
|
49
|
+
export type BaseName = `${string}${Extname}`
|
|
50
|
+
|
|
51
|
+
export type Path = string
|
|
52
|
+
|
|
53
|
+
export type AdvancedPath<T extends BaseName = BaseName> = `${BasePath}${T}`
|
|
54
|
+
|
|
55
|
+
export type OptionalPath = Path | undefined | null
|
|
56
|
+
|
|
57
|
+
export type FileMetaBase = {
|
|
58
|
+
pluginKey?: KubbPlugin['key']
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export type File<
|
|
62
|
+
TMeta extends FileMetaBase = FileMetaBase,
|
|
63
|
+
TBaseName extends BaseName = BaseName,
|
|
64
|
+
> = {
|
|
65
|
+
/**
|
|
66
|
+
* Unique identifier to reuse later
|
|
67
|
+
* @default crypto.randomUUID()
|
|
68
|
+
*/
|
|
69
|
+
id?: string
|
|
70
|
+
/**
|
|
71
|
+
* Name to be used to dynamicly create the baseName(based on input.path)
|
|
72
|
+
* Based on UNIX basename
|
|
73
|
+
* @link https://nodejs.org/api/path.html#pathbasenamepath-suffix
|
|
74
|
+
*/
|
|
75
|
+
baseName: TBaseName
|
|
76
|
+
/**
|
|
77
|
+
* Path will be full qualified path to a specified file
|
|
78
|
+
*/
|
|
79
|
+
path: AdvancedPath<TBaseName> | Path
|
|
80
|
+
source: Source
|
|
81
|
+
imports?: Import[]
|
|
82
|
+
exports?: Export[]
|
|
83
|
+
/**
|
|
84
|
+
* This will call fileManager.add instead of fileManager.addOrAppend, adding the source when the files already exists
|
|
85
|
+
* This will also ignore the combinefiles utils
|
|
86
|
+
* @default `false`
|
|
87
|
+
*/
|
|
88
|
+
override?: boolean
|
|
89
|
+
meta?: TMeta
|
|
90
|
+
/**
|
|
91
|
+
* This will override `process.env[key]` inside the `source`, see `getFileSource`.
|
|
92
|
+
*/
|
|
93
|
+
env?: NodeJS.ProcessEnv
|
|
94
|
+
validate?: boolean
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export type ResolvedFile<
|
|
98
|
+
TMeta extends FileMetaBase = FileMetaBase,
|
|
99
|
+
TBaseName extends BaseName = BaseName,
|
|
100
|
+
> = KubbFile.File<TMeta, TBaseName> & {
|
|
101
|
+
/**
|
|
102
|
+
* @default crypto.randomUUID()
|
|
103
|
+
*/
|
|
104
|
+
id: UUID
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
type CacheItem = KubbFile.ResolvedFile & {
|
|
109
|
+
cancel?: () => void
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
type AddResult<T extends Array<KubbFile.File>> = Promise<
|
|
113
|
+
Awaited<GreaterThan<T['length'], 1> extends true ? Promise<KubbFile.ResolvedFile[]> : Promise<KubbFile.ResolvedFile>>
|
|
114
|
+
>
|
|
115
|
+
|
|
116
|
+
type AddIndexesProps = {
|
|
117
|
+
root: KubbFile.Path
|
|
118
|
+
extName?: KubbFile.Extname
|
|
119
|
+
options?: BarrelManagerOptions
|
|
120
|
+
meta?: KubbFile.File['meta']
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
type Options = {
|
|
124
|
+
queue?: Queue
|
|
125
|
+
task?: QueueJob<KubbFile.ResolvedFile>
|
|
126
|
+
/**
|
|
127
|
+
* Timeout between writes
|
|
128
|
+
*/
|
|
129
|
+
timeout?: number
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
export class FileManager {
|
|
133
|
+
#cache: Map<KubbFile.Path, CacheItem[]> = new Map()
|
|
134
|
+
|
|
135
|
+
#task?: QueueJob<KubbFile.ResolvedFile>
|
|
136
|
+
#isWriting = false
|
|
137
|
+
/**
|
|
138
|
+
* Timeout between writes
|
|
139
|
+
*/
|
|
140
|
+
#timeout: number = 0
|
|
141
|
+
#queue?: Queue
|
|
142
|
+
|
|
143
|
+
constructor(options?: Options) {
|
|
144
|
+
if (options) {
|
|
145
|
+
this.#task = options.task
|
|
146
|
+
this.#queue = options.queue
|
|
147
|
+
this.#timeout = options.timeout || 0
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
return this
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
get files(): Array<KubbFile.File> {
|
|
154
|
+
const files: Array<KubbFile.File> = []
|
|
155
|
+
this.#cache.forEach((item) => {
|
|
156
|
+
files.push(...item.flat(1))
|
|
157
|
+
})
|
|
158
|
+
|
|
159
|
+
return files
|
|
160
|
+
}
|
|
161
|
+
get isExecuting(): boolean {
|
|
162
|
+
return this.#queue?.hasJobs ?? this.#isWriting ?? false
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
#validate(file: KubbFile.File): void {
|
|
166
|
+
if (!file.validate) {
|
|
167
|
+
return
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
if (!file.path.toLowerCase().endsWith(file.baseName.toLowerCase())) {
|
|
171
|
+
throw new Error(`${file.path} should end with the baseName ${file.baseName}`)
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
async add<T extends Array<KubbFile.File> = Array<KubbFile.File>>(
|
|
176
|
+
...files: T
|
|
177
|
+
): AddResult<T> {
|
|
178
|
+
const promises = files.map((file) => {
|
|
179
|
+
this.#validate(file)
|
|
180
|
+
|
|
181
|
+
if (file.override) {
|
|
182
|
+
return this.#add(file)
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
return this.#addOrAppend(file)
|
|
186
|
+
})
|
|
187
|
+
|
|
188
|
+
const resolvedFiles = await Promise.all(promises)
|
|
189
|
+
|
|
190
|
+
if (files.length > 1) {
|
|
191
|
+
return resolvedFiles as unknown as AddResult<T>
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
return resolvedFiles[0] as unknown as AddResult<T>
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
async #add(file: KubbFile.File): Promise<KubbFile.ResolvedFile> {
|
|
198
|
+
const controller = new AbortController()
|
|
199
|
+
const resolvedFile: KubbFile.ResolvedFile = { id: crypto.randomUUID(), ...file }
|
|
200
|
+
|
|
201
|
+
this.#cache.set(resolvedFile.path, [{ cancel: () => controller.abort(), ...resolvedFile }])
|
|
202
|
+
|
|
203
|
+
if (this.#queue) {
|
|
204
|
+
await this.#queue.run(
|
|
205
|
+
async () => {
|
|
206
|
+
return this.#task?.(resolvedFile)
|
|
207
|
+
},
|
|
208
|
+
{ controller },
|
|
209
|
+
)
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
return resolvedFile
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
async #addOrAppend(file: KubbFile.File): Promise<KubbFile.ResolvedFile> {
|
|
216
|
+
const previousCaches = this.#cache.get(file.path)
|
|
217
|
+
const previousCache = previousCaches ? previousCaches.at(previousCaches.length - 1) : undefined
|
|
218
|
+
|
|
219
|
+
if (previousCache) {
|
|
220
|
+
this.#cache.delete(previousCache.path)
|
|
221
|
+
|
|
222
|
+
return this.#add({
|
|
223
|
+
...file,
|
|
224
|
+
source: previousCache.source && file.source ? `${previousCache.source}\n${file.source}` : '',
|
|
225
|
+
imports: [...(previousCache.imports || []), ...(file.imports || [])],
|
|
226
|
+
exports: [...(previousCache.exports || []), ...(file.exports || [])],
|
|
227
|
+
env: { ...(previousCache.env || {}), ...(file.env || {}) },
|
|
228
|
+
})
|
|
229
|
+
}
|
|
230
|
+
return this.#add(file)
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
async addIndexes({ root, extName = '.ts', meta, options = {} }: AddIndexesProps): Promise<Array<KubbFile.File> | undefined> {
|
|
234
|
+
const barrelManager = new BarrelManager(options)
|
|
235
|
+
|
|
236
|
+
const files = barrelManager.getIndexes(root, extName)
|
|
237
|
+
|
|
238
|
+
if (!files) {
|
|
239
|
+
return undefined
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
return await Promise.all(
|
|
243
|
+
files.map((file) => {
|
|
244
|
+
return this.#addOrAppend({
|
|
245
|
+
...file,
|
|
246
|
+
meta: meta ? meta : file.meta,
|
|
247
|
+
})
|
|
248
|
+
}),
|
|
249
|
+
)
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
getCacheByUUID(UUID: KubbFile.UUID): KubbFile.File | undefined {
|
|
253
|
+
let cache: KubbFile.File | undefined
|
|
254
|
+
|
|
255
|
+
this.#cache.forEach((files) => {
|
|
256
|
+
cache = files.find((item) => item.id === UUID)
|
|
257
|
+
})
|
|
258
|
+
return cache
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
get(path: KubbFile.Path): Array<KubbFile.File> | undefined {
|
|
262
|
+
return this.#cache.get(path)
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
remove(path: KubbFile.Path): void {
|
|
266
|
+
const cacheItem = this.get(path)
|
|
267
|
+
if (!cacheItem) {
|
|
268
|
+
return
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
this.#cache.delete(path)
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
async write(...params: Parameters<typeof write>): Promise<string | undefined> {
|
|
275
|
+
if (!this.#isWriting) {
|
|
276
|
+
this.#isWriting = true
|
|
277
|
+
|
|
278
|
+
const text = await write(...params)
|
|
279
|
+
|
|
280
|
+
this.#isWriting = false
|
|
281
|
+
return text
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
await timeout(this.#timeout)
|
|
285
|
+
|
|
286
|
+
return this.write(...params)
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
async read(...params: Parameters<typeof read>): Promise<string> {
|
|
290
|
+
return read(...params)
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// statics
|
|
294
|
+
|
|
295
|
+
static getSource<TMeta extends KubbFile.FileMetaBase = KubbFile.FileMetaBase>(file: KubbFile.File<TMeta>): string {
|
|
296
|
+
if (!FileManager.isExtensionAllowed(file.baseName)) {
|
|
297
|
+
return file.source
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
const exports = file.exports ? combineExports(file.exports) : []
|
|
301
|
+
const imports = file.imports ? combineImports(file.imports, exports, file.source) : []
|
|
302
|
+
|
|
303
|
+
const importNodes = imports.map((item) => factory.createImportDeclaration({ name: item.name, path: item.path, isTypeOnly: item.isTypeOnly }))
|
|
304
|
+
const exportNodes = exports.map((item) =>
|
|
305
|
+
factory.createExportDeclaration({ name: item.name, path: item.path, isTypeOnly: item.isTypeOnly, asAlias: item.asAlias })
|
|
306
|
+
)
|
|
307
|
+
|
|
308
|
+
return [print([...importNodes, ...exportNodes]), getEnvSource(file.source, file.env)].join('\n')
|
|
309
|
+
}
|
|
310
|
+
static combineFiles<TMeta extends KubbFile.FileMetaBase = KubbFile.FileMetaBase>(files: Array<KubbFile.File<TMeta> | null>): Array<KubbFile.File<TMeta>> {
|
|
311
|
+
return files.filter(Boolean).reduce((acc, file: KubbFile.File<TMeta>) => {
|
|
312
|
+
const prevIndex = acc.findIndex((item) => item.path === file.path)
|
|
313
|
+
|
|
314
|
+
if (prevIndex === -1) {
|
|
315
|
+
return [...acc, file]
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
const prev = acc[prevIndex]
|
|
319
|
+
|
|
320
|
+
if (prev && file.override) {
|
|
321
|
+
acc[prevIndex] = {
|
|
322
|
+
imports: [],
|
|
323
|
+
exports: [],
|
|
324
|
+
...file,
|
|
325
|
+
}
|
|
326
|
+
return acc
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
if (prev) {
|
|
330
|
+
acc[prevIndex] = {
|
|
331
|
+
...file,
|
|
332
|
+
source: prev.source && file.source ? `${prev.source}\n${file.source}` : '',
|
|
333
|
+
imports: [...(prev.imports || []), ...(file.imports || [])],
|
|
334
|
+
exports: [...(prev.exports || []), ...(file.exports || [])],
|
|
335
|
+
env: { ...(prev.env || {}), ...(file.env || {}) },
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
return acc
|
|
340
|
+
}, [] as Array<KubbFile.File<TMeta>>)
|
|
341
|
+
}
|
|
342
|
+
static getMode(path: string | undefined | null): KubbFile.Mode {
|
|
343
|
+
if (!path) {
|
|
344
|
+
return 'directory'
|
|
345
|
+
}
|
|
346
|
+
return extname(path) ? 'file' : 'directory'
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
static get extensions(): Array<KubbFile.Extname> {
|
|
350
|
+
return ['.js', '.ts', '.tsx']
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
static isExtensionAllowed(baseName: string): boolean {
|
|
354
|
+
return FileManager.extensions.some((extension) => baseName.endsWith(extension))
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
export function combineExports(exports: Array<KubbFile.Export>): Array<KubbFile.Export> {
|
|
359
|
+
const combinedExports = orderBy(exports, [(v) => !v.isTypeOnly], ['asc']).reduce((prev, curr) => {
|
|
360
|
+
const name = curr.name
|
|
361
|
+
const prevByPath = prev.findLast((imp) => imp.path === curr.path)
|
|
362
|
+
const prevByPathAndIsTypeOnly = prev.findLast((imp) => imp.path === curr.path && isEqual(imp.name, name) && imp.isTypeOnly)
|
|
363
|
+
|
|
364
|
+
if (prevByPathAndIsTypeOnly) {
|
|
365
|
+
// we already have an export that has the same path but uses `isTypeOnly` (export type ...)
|
|
366
|
+
return prev
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
const uniquePrev = prev.findLast(
|
|
370
|
+
(imp) => imp.path === curr.path && isEqual(imp.name, name) && imp.isTypeOnly === curr.isTypeOnly && imp.asAlias === curr.asAlias,
|
|
371
|
+
)
|
|
372
|
+
|
|
373
|
+
if (uniquePrev || (Array.isArray(name) && !name.length) || (prevByPath?.asAlias && !curr.asAlias)) {
|
|
374
|
+
return prev
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
if (!prevByPath) {
|
|
378
|
+
return [
|
|
379
|
+
...prev,
|
|
380
|
+
{
|
|
381
|
+
...curr,
|
|
382
|
+
name: Array.isArray(name) ? [...new Set(name)] : name,
|
|
383
|
+
},
|
|
384
|
+
]
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
if (prevByPath && Array.isArray(prevByPath.name) && Array.isArray(curr.name) && prevByPath.isTypeOnly === curr.isTypeOnly) {
|
|
388
|
+
prevByPath.name = [...new Set([...prevByPath.name, ...curr.name])]
|
|
389
|
+
|
|
390
|
+
return prev
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
return [...prev, curr]
|
|
394
|
+
}, [] as Array<KubbFile.Export>)
|
|
395
|
+
|
|
396
|
+
return orderBy(combinedExports, [(v) => !v.isTypeOnly, (v) => v.asAlias], ['desc', 'desc'])
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
export function combineImports(imports: Array<KubbFile.Import>, exports: Array<KubbFile.Export>, source?: string): Array<KubbFile.Import> {
|
|
400
|
+
const combinedImports = orderBy(imports, [(v) => !v.isTypeOnly], ['asc']).reduce((prev, curr) => {
|
|
401
|
+
let name = Array.isArray(curr.name) ? [...new Set(curr.name)] : curr.name
|
|
402
|
+
|
|
403
|
+
const hasImportInSource = (importName: string) => {
|
|
404
|
+
if (!source) {
|
|
405
|
+
return true
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
const checker = (name?: string) => name && !!source.includes(name)
|
|
409
|
+
return checker(importName) || exports.some(({ name }) => (Array.isArray(name) ? name.some(checker) : checker(name)))
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
if (Array.isArray(name)) {
|
|
413
|
+
name = name.filter((item) => hasImportInSource(item))
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
const prevByPath = prev.findLast((imp) => imp.path === curr.path && imp.isTypeOnly === curr.isTypeOnly)
|
|
417
|
+
const uniquePrev = prev.findLast((imp) => imp.path === curr.path && isEqual(imp.name, name) && imp.isTypeOnly === curr.isTypeOnly)
|
|
418
|
+
const prevByPathNameAndIsTypeOnly = prev.findLast((imp) => imp.path === curr.path && isEqual(imp.name, name) && imp.isTypeOnly)
|
|
419
|
+
|
|
420
|
+
if (prevByPathNameAndIsTypeOnly) {
|
|
421
|
+
// we already have an export that has the same path but uses `isTypeOnly` (import type ...)
|
|
422
|
+
return prev
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
if (uniquePrev || (Array.isArray(name) && !name.length)) {
|
|
426
|
+
return prev
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
if (!prevByPath) {
|
|
430
|
+
return [
|
|
431
|
+
...prev,
|
|
432
|
+
{
|
|
433
|
+
...curr,
|
|
434
|
+
name,
|
|
435
|
+
},
|
|
436
|
+
]
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
if (prevByPath && Array.isArray(prevByPath.name) && Array.isArray(name) && prevByPath.isTypeOnly === curr.isTypeOnly) {
|
|
440
|
+
prevByPath.name = [...new Set([...prevByPath.name, ...name])]
|
|
441
|
+
|
|
442
|
+
return prev
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
if (!Array.isArray(name) && name && !hasImportInSource(name)) {
|
|
446
|
+
return prev
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
return [...prev, curr]
|
|
450
|
+
}, [] as Array<KubbFile.Import>)
|
|
451
|
+
|
|
452
|
+
return orderBy(combinedImports, [(v) => !v.isTypeOnly], ['desc'])
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
function getEnvSource(source: string, env: NodeJS.ProcessEnv | undefined): string {
|
|
456
|
+
if (!env) {
|
|
457
|
+
return source
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
const keys = Object.keys(env)
|
|
461
|
+
|
|
462
|
+
if (!keys.length) {
|
|
463
|
+
return source
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
return keys.reduce((prev, key: string) => {
|
|
467
|
+
const environmentValue = env[key]
|
|
468
|
+
const replaceBy = environmentValue ? `'${environmentValue.replaceAll('"', '')?.replaceAll("'", '')}'` : 'undefined'
|
|
469
|
+
|
|
470
|
+
if (key.toUpperCase() !== key) {
|
|
471
|
+
throw new TypeError(`Environment should be in upperCase for ${key}`)
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
if (typeof replaceBy === 'string') {
|
|
475
|
+
prev = transformers.searchAndReplace({ text: prev.replaceAll(`process.env.${key}`, replaceBy), replaceBy, prefix: 'process.env', key })
|
|
476
|
+
// removes `declare const ...`
|
|
477
|
+
prev = transformers.searchAndReplace({ text: prev.replaceAll(new RegExp(`(declare const).*\n`, 'ig'), ''), replaceBy, key })
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
return prev
|
|
481
|
+
}, source)
|
|
482
|
+
}
|
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
|
+
}
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import mod from 'node:module'
|
|
2
|
+
import os from 'node:os'
|
|
3
|
+
import { pathToFileURL } from 'node:url'
|
|
4
|
+
|
|
5
|
+
import { findUp, findUpSync } from 'find-up'
|
|
6
|
+
import { coerce, satisfies } from 'semver'
|
|
7
|
+
|
|
8
|
+
type PackageJSON = {
|
|
9
|
+
dependencies?: Record<string, string>
|
|
10
|
+
devDependencies?: Record<string, string>
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
type DependencyName = string
|
|
14
|
+
|
|
15
|
+
type DependencyVersion = string
|
|
16
|
+
|
|
17
|
+
export class PackageManager {
|
|
18
|
+
static #cache: Record<DependencyName, DependencyVersion> = {}
|
|
19
|
+
|
|
20
|
+
#cwd?: string
|
|
21
|
+
#SLASHES = new Set(['/', '\\'])
|
|
22
|
+
constructor(workspace?: string) {
|
|
23
|
+
if (workspace) {
|
|
24
|
+
this.#cwd = workspace
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return this
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
set workspace(workspace: string) {
|
|
31
|
+
this.#cwd = workspace
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
get workspace(): string | undefined {
|
|
35
|
+
return this.#cwd
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
normalizeDirectory(directory: string): string {
|
|
39
|
+
if (!this.#SLASHES.has(directory[directory.length - 1]!)) {
|
|
40
|
+
return `${directory}/`
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return directory
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
getLocation(path: string): string {
|
|
47
|
+
let location = path
|
|
48
|
+
|
|
49
|
+
if (this.#cwd) {
|
|
50
|
+
const require = mod.createRequire(this.normalizeDirectory(this.#cwd))
|
|
51
|
+
location = require.resolve(path)
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return location
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
async import(path: string): Promise<any | undefined> {
|
|
58
|
+
try {
|
|
59
|
+
let location = this.getLocation(path)
|
|
60
|
+
|
|
61
|
+
if (os.platform() == 'win32') {
|
|
62
|
+
location = pathToFileURL(location).href
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
|
66
|
+
const module = await import(location)
|
|
67
|
+
|
|
68
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-return
|
|
69
|
+
return module?.default ?? module
|
|
70
|
+
} catch (e) {
|
|
71
|
+
console.log(e)
|
|
72
|
+
return undefined
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
async getPackageJSON(): Promise<PackageJSON | undefined> {
|
|
77
|
+
const pkgPath = await findUp(['package.json'], {
|
|
78
|
+
cwd: this.#cwd,
|
|
79
|
+
})
|
|
80
|
+
if (!pkgPath) {
|
|
81
|
+
return undefined
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return require(pkgPath) as PackageJSON
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
getPackageJSONSync(): PackageJSON | undefined {
|
|
88
|
+
const pkgPath = findUpSync(['package.json'], {
|
|
89
|
+
cwd: this.#cwd,
|
|
90
|
+
})
|
|
91
|
+
if (!pkgPath) {
|
|
92
|
+
return undefined
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return require(pkgPath) as PackageJSON
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
static setVersion(dependency: DependencyName, version: DependencyVersion): void {
|
|
99
|
+
PackageManager.#cache[dependency] = version
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
async getVersion(dependency: DependencyName): Promise<DependencyVersion | undefined> {
|
|
103
|
+
if (PackageManager.#cache[dependency]) {
|
|
104
|
+
return PackageManager.#cache[dependency]
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const packageJSON = await this.getPackageJSON()
|
|
108
|
+
|
|
109
|
+
if (!packageJSON) {
|
|
110
|
+
return undefined
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
return packageJSON['dependencies']?.[dependency] || packageJSON['devDependencies']?.[dependency]
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
getVersionSync(dependency: DependencyName): DependencyVersion | undefined {
|
|
117
|
+
if (PackageManager.#cache[dependency]) {
|
|
118
|
+
return PackageManager.#cache[dependency]
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const packageJSON = this.getPackageJSONSync()
|
|
122
|
+
|
|
123
|
+
if (!packageJSON) {
|
|
124
|
+
return undefined
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
return packageJSON['dependencies']?.[dependency] || packageJSON['devDependencies']?.[dependency]
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
async isValid(dependency: DependencyName, version: DependencyVersion): Promise<boolean> {
|
|
131
|
+
const packageVersion = await this.getVersion(dependency)
|
|
132
|
+
|
|
133
|
+
if (!packageVersion) {
|
|
134
|
+
return false
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
if (packageVersion === version) {
|
|
138
|
+
return true
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const semVer = coerce(packageVersion)
|
|
142
|
+
|
|
143
|
+
if (!semVer) {
|
|
144
|
+
throw new Error(`${packageVersion} is not valid`)
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
return satisfies(semVer, version)
|
|
148
|
+
}
|
|
149
|
+
isValidSync(dependency: DependencyName, version: DependencyVersion): boolean {
|
|
150
|
+
const packageVersion = this.getVersionSync(dependency)
|
|
151
|
+
|
|
152
|
+
if (!packageVersion) {
|
|
153
|
+
return false
|
|
154
|
+
}
|
|
155
|
+
const semVer = coerce(packageVersion)
|
|
156
|
+
|
|
157
|
+
if (!semVer) {
|
|
158
|
+
throw new Error(`${packageVersion} is not valid`)
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
return satisfies(semVer, version)
|
|
162
|
+
}
|
|
163
|
+
}
|