@kubb/fabric-core 0.0.1 → 0.1.1

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 (65) hide show
  1. package/dist/{types-lS0JaZqX.d.cts → KubbFile-BrN7Wwp6.d.cts} +3 -3
  2. package/dist/{types-BY5X8xoR.d.ts → KubbFile-BzVkcu9M.d.ts} +3 -3
  3. package/dist/createFileParser-BD8yn0LT.cjs +14 -0
  4. package/dist/createFileParser-BD8yn0LT.cjs.map +1 -0
  5. package/dist/createFileParser-Cix3AMLd.js +8 -0
  6. package/dist/createFileParser-Cix3AMLd.js.map +1 -0
  7. package/dist/default-DCpuPmrL.js +10 -0
  8. package/dist/default-DCpuPmrL.js.map +1 -0
  9. package/dist/default-DNBu_jsL.cjs +15 -0
  10. package/dist/default-DNBu_jsL.cjs.map +1 -0
  11. package/dist/defineApp-CZYKsxTp.d.ts +95 -0
  12. package/dist/defineApp-c9lWJ96_.d.cts +95 -0
  13. package/dist/index.cjs +146 -87
  14. package/dist/index.cjs.map +1 -1
  15. package/dist/index.d.cts +13 -60
  16. package/dist/index.d.ts +13 -60
  17. package/dist/index.js +136 -76
  18. package/dist/index.js.map +1 -1
  19. package/dist/parsers/default.cjs +4 -0
  20. package/dist/parsers/default.d.cts +8 -0
  21. package/dist/parsers/default.d.ts +8 -0
  22. package/dist/parsers/default.js +4 -0
  23. package/dist/parsers/tsx.cjs +4 -2
  24. package/dist/parsers/tsx.d.cts +3 -3
  25. package/dist/parsers/tsx.d.ts +3 -3
  26. package/dist/parsers/tsx.js +3 -1
  27. package/dist/parsers/typescript.cjs +6 -5
  28. package/dist/parsers/typescript.d.cts +3 -3
  29. package/dist/parsers/typescript.d.ts +3 -3
  30. package/dist/parsers/typescript.js +2 -1
  31. package/dist/tsx-BSUaIML3.cjs +16 -0
  32. package/dist/tsx-BSUaIML3.cjs.map +1 -0
  33. package/dist/tsx-DBAk9dqS.js +11 -0
  34. package/dist/tsx-DBAk9dqS.js.map +1 -0
  35. package/dist/types-CkbelZaS.d.ts +15 -0
  36. package/dist/types-GueHciQ3.d.cts +15 -0
  37. package/dist/types.cjs +12 -0
  38. package/dist/types.cjs.map +1 -0
  39. package/dist/types.d.cts +3 -0
  40. package/dist/types.d.ts +3 -0
  41. package/dist/types.js +6 -0
  42. package/dist/types.js.map +1 -0
  43. package/dist/{parser-CWB_OBtr.js → typescript-C60gWBu8.js} +4 -34
  44. package/dist/typescript-C60gWBu8.js.map +1 -0
  45. package/dist/{parser-QF8j8-pj.cjs → typescript-Z90jN87k.cjs} +5 -47
  46. package/dist/typescript-Z90jN87k.cjs.map +1 -0
  47. package/package.json +15 -1
  48. package/src/FileManager.ts +21 -200
  49. package/src/FileProcessor.ts +86 -0
  50. package/src/KubbFile.ts +132 -0
  51. package/src/createFile.ts +167 -0
  52. package/src/defineApp.ts +6 -6
  53. package/src/index.ts +4 -2
  54. package/src/parsers/createFileParser.ts +5 -0
  55. package/src/parsers/default.ts +7 -0
  56. package/src/parsers/tsx.ts +1 -1
  57. package/src/parsers/types.ts +12 -0
  58. package/src/parsers/typescript.ts +1 -1
  59. package/src/types.ts +2 -132
  60. package/src/utils/EventEmitter.ts +23 -0
  61. package/dist/parser-Bck6QDwN.d.cts +0 -15
  62. package/dist/parser-CRl-iUw1.d.ts +0 -15
  63. package/dist/parser-CWB_OBtr.js.map +0 -1
  64. package/dist/parser-QF8j8-pj.cjs.map +0 -1
  65. package/src/parsers/parser.ts +0 -56
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kubb/fabric-core",
3
- "version": "0.0.1",
3
+ "version": "0.1.1",
4
4
  "description": "Core functionality for Kubb's plugin-based code generation system, providing the foundation for transforming OpenAPI specifications.",
5
5
  "keywords": [
6
6
  "typescript",
@@ -26,6 +26,10 @@
26
26
  "import": "./dist/index.js",
27
27
  "require": "./dist/index.cjs"
28
28
  },
29
+ "./parsers/default": {
30
+ "import": "./dist/parsers/default.js",
31
+ "require": "./dist/parsers/default.cjs"
32
+ },
29
33
  "./parsers/tsx": {
30
34
  "import": "./dist/parsers/tsx.js",
31
35
  "require": "./dist/parsers/tsx.cjs"
@@ -34,6 +38,10 @@
34
38
  "import": "./dist/parsers/typescript.js",
35
39
  "require": "./dist/parsers/typescript.cjs"
36
40
  },
41
+ "./types": {
42
+ "import": "./dist/types.js",
43
+ "require": "./dist/types.cjs"
44
+ },
37
45
  "./package.json": "./package.json"
38
46
  },
39
47
  "main": "./dist/index.cjs",
@@ -46,6 +54,12 @@
46
54
  ],
47
55
  "parsers/tsx": [
48
56
  "./dist/parsers/tsx.d.ts"
57
+ ],
58
+ "parsers/default": [
59
+ "./dist/parsers/default.d.ts"
60
+ ],
61
+ "types": [
62
+ "./dist/types.d.ts"
49
63
  ]
50
64
  }
51
65
  },
@@ -1,22 +1,22 @@
1
- import pLimit from 'p-limit'
2
-
3
- import type * as KubbFile from './types.ts'
4
- import { parseFile } from './parsers/parser.ts'
1
+ import type * as KubbFile from './KubbFile.ts'
5
2
  import { Cache } from './utils/Cache.ts'
6
- import { trimExtName, write } from './fs.ts'
7
- import { createHash } from 'node:crypto'
8
- import path from 'node:path'
3
+ import { trimExtName } from './fs.ts'
9
4
  import { orderBy } from 'natural-orderby'
10
- import { isDeepEqual, uniqueBy } from 'remeda'
5
+ import { createFile } from './createFile.ts'
6
+ import { FileProcessor, type ProcessFilesProps } from './FileProcessor.ts'
11
7
 
12
- type WriteFilesProps = {
13
- extension?: Record<KubbFile.Extname, KubbFile.Extname | ''>
14
- dryRun?: boolean
8
+ function mergeFile<TMeta extends object = object>(a: KubbFile.File<TMeta>, b: KubbFile.File<TMeta>): KubbFile.File<TMeta> {
9
+ return {
10
+ ...a,
11
+ sources: [...(a.sources || []), ...(b.sources || [])],
12
+ imports: [...(a.imports || []), ...(b.imports || [])],
13
+ exports: [...(a.exports || []), ...(b.exports || [])],
14
+ }
15
15
  }
16
16
 
17
17
  export class FileManager {
18
18
  #cache = new Cache<KubbFile.ResolvedFile>()
19
- #limit = pLimit(100)
19
+ #processor = new FileProcessor()
20
20
 
21
21
  constructor() {
22
22
  return this
@@ -67,7 +67,7 @@ export class FileManager {
67
67
  this.#cache.clear()
68
68
  }
69
69
 
70
- getFiles(): Array<KubbFile.ResolvedFile> {
70
+ get files(): Array<KubbFile.ResolvedFile> {
71
71
  const cachedKeys = this.#cache.keys()
72
72
 
73
73
  // order by path length and if file is a barrel file
@@ -78,193 +78,14 @@ export class FileManager {
78
78
  return files.filter(Boolean)
79
79
  }
80
80
 
81
- async processFiles({ dryRun, extension }: WriteFilesProps): Promise<Array<KubbFile.ResolvedFile>> {
82
- const files = this.getFiles()
83
-
84
- const promises = files.map((resolvedFile) => {
85
- return this.#limit(async () => {
86
- const extname = extension ? extension[resolvedFile.extname] || undefined : resolvedFile.extname
87
-
88
- if (!dryRun) {
89
- const source = await parseFile(resolvedFile, { extname })
90
-
91
- await write(resolvedFile.path, source, { sanity: false })
92
- }
93
- })
94
- })
95
-
96
- await Promise.all(promises)
97
-
98
- return files
99
- }
100
- }
101
-
102
- function hashObject(obj: Record<string, unknown>): string {
103
- const str = JSON.stringify(obj, Object.keys(obj).sort())
104
- return createHash('sha256').update(str).digest('hex')
105
- }
106
-
107
- export function mergeFile<TMeta extends object = object>(a: KubbFile.File<TMeta>, b: KubbFile.File<TMeta>): KubbFile.File<TMeta> {
108
- return {
109
- ...a,
110
- sources: [...(a.sources || []), ...(b.sources || [])],
111
- imports: [...(a.imports || []), ...(b.imports || [])],
112
- exports: [...(a.exports || []), ...(b.exports || [])],
113
- }
114
- }
115
-
116
- export function combineSources(sources: Array<KubbFile.Source>): Array<KubbFile.Source> {
117
- return uniqueBy(sources, (obj) => [obj.name, obj.isExportable, obj.isTypeOnly] as const)
118
- }
119
-
120
- export function combineExports(exports: Array<KubbFile.Export>): Array<KubbFile.Export> {
121
- return orderBy(exports, [
122
- (v) => !!Array.isArray(v.name),
123
- (v) => !v.isTypeOnly,
124
- (v) => v.path,
125
- (v) => !!v.name,
126
- (v) => (Array.isArray(v.name) ? orderBy(v.name) : v.name),
127
- ]).reduce(
128
- (prev, curr) => {
129
- const name = curr.name
130
- const prevByPath = prev.findLast((imp) => imp.path === curr.path)
131
- const prevByPathAndIsTypeOnly = prev.findLast((imp) => imp.path === curr.path && isDeepEqual(imp.name, name) && imp.isTypeOnly)
132
-
133
- if (prevByPathAndIsTypeOnly) {
134
- // we already have an export that has the same path but uses `isTypeOnly` (export type ...)
135
- return prev
136
- }
137
-
138
- const uniquePrev = prev.findLast(
139
- (imp) => imp.path === curr.path && isDeepEqual(imp.name, name) && imp.isTypeOnly === curr.isTypeOnly && imp.asAlias === curr.asAlias,
140
- )
141
-
142
- // we already have an item that was unique enough or name field is empty or prev asAlias is set but current has no changes
143
- if (uniquePrev || (Array.isArray(name) && !name.length) || (prevByPath?.asAlias && !curr.asAlias)) {
144
- return prev
145
- }
146
-
147
- if (!prevByPath) {
148
- return [
149
- ...prev,
150
- {
151
- ...curr,
152
- name: Array.isArray(name) ? [...new Set(name)] : name,
153
- },
154
- ]
155
- }
156
-
157
- // merge all names when prev and current both have the same isTypeOnly set
158
- if (prevByPath && Array.isArray(prevByPath.name) && Array.isArray(curr.name) && prevByPath.isTypeOnly === curr.isTypeOnly) {
159
- prevByPath.name = [...new Set([...prevByPath.name, ...curr.name])]
160
-
161
- return prev
162
- }
163
-
164
- return [...prev, curr]
165
- },
166
- [] as Array<KubbFile.Export>,
167
- )
168
- }
169
-
170
- export function combineImports(imports: Array<KubbFile.Import>, exports: Array<KubbFile.Export>, source?: string): Array<KubbFile.Import> {
171
- return orderBy(imports, [
172
- (v) => !!Array.isArray(v.name),
173
- (v) => !v.isTypeOnly,
174
- (v) => v.path,
175
- (v) => !!v.name,
176
- (v) => (Array.isArray(v.name) ? orderBy(v.name) : v.name),
177
- ]).reduce(
178
- (prev, curr) => {
179
- let name = Array.isArray(curr.name) ? [...new Set(curr.name)] : curr.name
180
-
181
- const hasImportInSource = (importName: string) => {
182
- if (!source) {
183
- return true
184
- }
185
-
186
- const checker = (name?: string) => {
187
- return name && source.includes(name)
188
- }
189
-
190
- return checker(importName) || exports.some(({ name }) => (Array.isArray(name) ? name.some(checker) : checker(name)))
191
- }
192
-
193
- if (curr.path === curr.root) {
194
- // root and path are the same file, remove the "./" import
195
- return prev
196
- }
197
-
198
- // merge all names and check if the importName is being used in the generated source and if not filter those imports out
199
- if (Array.isArray(name)) {
200
- name = name.filter((item) => (typeof item === 'string' ? hasImportInSource(item) : hasImportInSource(item.propertyName)))
201
- }
202
-
203
- const prevByPath = prev.findLast((imp) => imp.path === curr.path && imp.isTypeOnly === curr.isTypeOnly)
204
- const uniquePrev = prev.findLast((imp) => imp.path === curr.path && isDeepEqual(imp.name, name) && imp.isTypeOnly === curr.isTypeOnly)
205
- const prevByPathNameAndIsTypeOnly = prev.findLast((imp) => imp.path === curr.path && isDeepEqual(imp.name, name) && imp.isTypeOnly)
206
-
207
- if (prevByPathNameAndIsTypeOnly) {
208
- // we already have an export that has the same path but uses `isTypeOnly` (import type ...)
209
- return prev
210
- }
211
-
212
- // already unique enough or name is empty
213
- if (uniquePrev || (Array.isArray(name) && !name.length)) {
214
- return prev
215
- }
216
-
217
- // new item, append name
218
- if (!prevByPath) {
219
- return [
220
- ...prev,
221
- {
222
- ...curr,
223
- name,
224
- },
225
- ]
226
- }
227
-
228
- // merge all names when prev and current both have the same isTypeOnly set
229
- if (prevByPath && Array.isArray(prevByPath.name) && Array.isArray(name) && prevByPath.isTypeOnly === curr.isTypeOnly) {
230
- prevByPath.name = [...new Set([...prevByPath.name, ...name])]
231
-
232
- return prev
233
- }
234
-
235
- // no import was found in the source, ignore import
236
- if (!Array.isArray(name) && name && !hasImportInSource(name)) {
237
- return prev
238
- }
239
-
240
- return [...prev, curr]
241
- },
242
- [] as Array<KubbFile.Import>,
243
- )
244
- }
245
-
246
- /**
247
- * Helper to create a file with name and id set
248
- */
249
- export function createFile<TMeta extends object = object>(file: KubbFile.File<TMeta>): KubbFile.ResolvedFile<TMeta> {
250
- const extname = path.extname(file.baseName) as KubbFile.Extname
251
- if (!extname) {
252
- throw new Error(`No extname found for ${file.baseName}`)
253
- }
254
-
255
- const source = file.sources.map((item) => item.value).join('\n\n')
256
- const exports = file.exports?.length ? combineExports(file.exports) : []
257
- const imports = file.imports?.length && source ? combineImports(file.imports, exports, source) : []
258
- const sources = file.sources?.length ? combineSources(file.sources) : []
81
+ get processor() {
82
+ const files = this.files
83
+ const processor = this.#processor
259
84
 
260
- return {
261
- ...file,
262
- id: hashObject({ path: file.path }),
263
- name: trimExtName(file.baseName),
264
- extname,
265
- imports: imports,
266
- exports: exports,
267
- sources: sources,
268
- meta: file.meta || ({} as TMeta),
85
+ return {
86
+ async run(options: ProcessFilesProps) {
87
+ return processor.run(files, options)
88
+ },
89
+ }
269
90
  }
270
91
  }
@@ -0,0 +1,86 @@
1
+ import type * as KubbFile from './KubbFile.ts'
2
+ import { EventEmitter } from './utils/EventEmitter.ts'
3
+ import { write } from './fs.ts'
4
+ import pLimit from 'p-limit'
5
+ import type { Parser } from './parsers/types.ts'
6
+ import { typeScriptParser } from './parsers/typescript.ts'
7
+ import { tsxParser } from './parsers/tsx.ts'
8
+ import { defaultParser } from './parsers/default.ts'
9
+
10
+ type FileProcessorEvents = {
11
+ start: [{ files: KubbFile.ResolvedFile[] }]
12
+ finish: [{ files: KubbFile.ResolvedFile[] }]
13
+ 'file:start': [{ file: KubbFile.ResolvedFile }]
14
+ 'file:finish': [{ file: KubbFile.ResolvedFile }]
15
+ }
16
+
17
+ export type ProcessFilesProps = {
18
+ extension?: Record<KubbFile.Extname, KubbFile.Extname | ''>
19
+ dryRun?: boolean
20
+ }
21
+
22
+ type GetSourceOptions = {
23
+ extname?: KubbFile.Extname
24
+ }
25
+
26
+ async function getParser<TMeta extends object = object>(extname: KubbFile.Extname | undefined): Promise<Parser<TMeta>> {
27
+ const parsers: Record<KubbFile.Extname, Parser<any>> = {
28
+ '.ts': typeScriptParser,
29
+ '.js': typeScriptParser,
30
+ '.jsx': tsxParser,
31
+ '.tsx': tsxParser,
32
+ '.json': defaultParser,
33
+ }
34
+
35
+ if (!extname) {
36
+ return defaultParser
37
+ }
38
+
39
+ const parser = parsers[extname]
40
+
41
+ if (!parser) {
42
+ console.warn(`[parser] No parser found for ${extname}, default parser will be used`)
43
+ }
44
+
45
+ return parser || defaultParser
46
+ }
47
+
48
+ export async function parseFile(file: KubbFile.ResolvedFile, { extname }: GetSourceOptions = {}): Promise<string> {
49
+ const parser = await getParser(file.extname)
50
+
51
+ return parser.print(file, { extname })
52
+ }
53
+
54
+ export class FileProcessor extends EventEmitter<FileProcessorEvents> {
55
+ #limit = pLimit(100)
56
+
57
+ constructor(maxListener = 1000) {
58
+ super(maxListener)
59
+ return this
60
+ }
61
+
62
+ async run(files: Array<KubbFile.ResolvedFile>, { dryRun, extension }: ProcessFilesProps): Promise<KubbFile.ResolvedFile[]> {
63
+ this.emit('start', { files })
64
+
65
+ const promises = files.map((resolvedFile) =>
66
+ this.#limit(async () => {
67
+ const extname = extension?.[resolvedFile.extname] || undefined
68
+
69
+ this.emit('file:start', { file: resolvedFile })
70
+
71
+ if (!dryRun) {
72
+ const source = await parseFile(resolvedFile, { extname })
73
+ await write(resolvedFile.path, source, { sanity: false })
74
+ }
75
+
76
+ this.emit('file:finish', { file: resolvedFile })
77
+ }),
78
+ )
79
+
80
+ await Promise.all(promises)
81
+
82
+ this.emit('finish', { files })
83
+
84
+ return files
85
+ }
86
+ }
@@ -0,0 +1,132 @@
1
+ type BasePath<T extends string = string> = `${T}/`
2
+
3
+ export type Import = {
4
+ /**
5
+ * Import name to be used
6
+ * @example ["useState"]
7
+ * @example "React"
8
+ */
9
+ name:
10
+ | string
11
+ | Array<
12
+ | string
13
+ | {
14
+ propertyName: string
15
+ name?: string
16
+ }
17
+ >
18
+ /**
19
+ * Path for the import
20
+ * @example '@kubb/core'
21
+ */
22
+ path: string
23
+ /**
24
+ * Add `type` prefix to the import, this will result in: `import type { Type } from './path'`.
25
+ */
26
+ isTypeOnly?: boolean
27
+
28
+ isNameSpace?: boolean
29
+ /**
30
+ * When root is set it will get the path with relative getRelativePath(root, path).
31
+ */
32
+ root?: string
33
+ }
34
+
35
+ export type Source = {
36
+ name?: string
37
+ value?: string
38
+ isTypeOnly?: boolean
39
+ /**
40
+ * Has const or type 'export'
41
+ * @default false
42
+ */
43
+ isExportable?: boolean
44
+ /**
45
+ * When set, barrel generation will add this
46
+ * @default false
47
+ */
48
+ isIndexable?: boolean
49
+ }
50
+
51
+ export type Export = {
52
+ /**
53
+ * Export name to be used.
54
+ * @example ["useState"]
55
+ * @example "React"
56
+ */
57
+ name?: string | Array<string>
58
+ /**
59
+ * Path for the import.
60
+ * @example '@kubb/core'
61
+ */
62
+ path: string
63
+ /**
64
+ * Add `type` prefix to the export, this will result in: `export type { Type } from './path'`.
65
+ */
66
+ isTypeOnly?: boolean
67
+ /**
68
+ * Make it possible to override the name, this will result in: `export * as aliasName from './path'`.
69
+ */
70
+ asAlias?: boolean
71
+ }
72
+
73
+ export type Extname = '.ts' | '.js' | '.tsx' | '.json' | `.${string}`
74
+
75
+ export type Mode = 'single' | 'split'
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}.${string}`
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 File<TMeta extends object = object> = {
94
+ /**
95
+ * Name to be used to create the path
96
+ * Based on UNIX basename, `${name}.extname`
97
+ * @link https://nodejs.org/api/path.html#pathbasenamepath-suffix
98
+ */
99
+ baseName: BaseName
100
+ /**
101
+ * Path will be full qualified path to a specified file
102
+ */
103
+ path: AdvancedPath<BaseName> | Path
104
+ sources: Array<Source>
105
+ imports?: Array<Import>
106
+ exports?: Array<Export>
107
+ /**
108
+ * Use extra meta, this is getting used to generate the barrel/index files.
109
+ */
110
+ meta?: TMeta
111
+ banner?: string
112
+ footer?: string
113
+ }
114
+
115
+ export type ResolvedImport = Import
116
+
117
+ export type ResolvedExport = Export
118
+
119
+ export type ResolvedFile<TMeta extends object = object> = File<TMeta> & {
120
+ /**
121
+ * @default hash
122
+ */
123
+ id: string
124
+ /**
125
+ * Contains the first part of the baseName, generated based on baseName
126
+ * @link https://nodejs.org/api/path.html#pathformatpathobject
127
+ */
128
+ name: string
129
+ extname: Extname
130
+ imports: Array<ResolvedImport>
131
+ exports: Array<ResolvedExport>
132
+ }
@@ -0,0 +1,167 @@
1
+ import type * as KubbFile from './KubbFile.ts'
2
+ import { trimExtName } from './fs.ts'
3
+ import { createHash } from 'node:crypto'
4
+ import path from 'node:path'
5
+ import { isDeepEqual, uniqueBy } from 'remeda'
6
+ import { orderBy } from 'natural-orderby'
7
+
8
+ function hashObject(obj: Record<string, unknown>): string {
9
+ const str = JSON.stringify(obj, Object.keys(obj).sort())
10
+ return createHash('sha256').update(str).digest('hex')
11
+ }
12
+
13
+ export function combineSources(sources: Array<KubbFile.Source>): Array<KubbFile.Source> {
14
+ return uniqueBy(sources, (obj) => [obj.name, obj.isExportable, obj.isTypeOnly] as const)
15
+ }
16
+
17
+ export function combineExports(exports: Array<KubbFile.Export>): Array<KubbFile.Export> {
18
+ return orderBy(exports, [
19
+ (v) => !!Array.isArray(v.name),
20
+ (v) => !v.isTypeOnly,
21
+ (v) => v.path,
22
+ (v) => !!v.name,
23
+ (v) => (Array.isArray(v.name) ? orderBy(v.name) : v.name),
24
+ ]).reduce(
25
+ (prev, curr) => {
26
+ const name = curr.name
27
+ const prevByPath = prev.findLast((imp) => imp.path === curr.path)
28
+ const prevByPathAndIsTypeOnly = prev.findLast((imp) => imp.path === curr.path && isDeepEqual(imp.name, name) && imp.isTypeOnly)
29
+
30
+ if (prevByPathAndIsTypeOnly) {
31
+ // we already have an export that has the same path but uses `isTypeOnly` (export type ...)
32
+ return prev
33
+ }
34
+
35
+ const uniquePrev = prev.findLast(
36
+ (imp) => imp.path === curr.path && isDeepEqual(imp.name, name) && imp.isTypeOnly === curr.isTypeOnly && imp.asAlias === curr.asAlias,
37
+ )
38
+
39
+ // we already have an item that was unique enough or name field is empty or prev asAlias is set but current has no changes
40
+ if (uniquePrev || (Array.isArray(name) && !name.length) || (prevByPath?.asAlias && !curr.asAlias)) {
41
+ return prev
42
+ }
43
+
44
+ if (!prevByPath) {
45
+ return [
46
+ ...prev,
47
+ {
48
+ ...curr,
49
+ name: Array.isArray(name) ? [...new Set(name)] : name,
50
+ },
51
+ ]
52
+ }
53
+
54
+ // merge all names when prev and current both have the same isTypeOnly set
55
+ if (prevByPath && Array.isArray(prevByPath.name) && Array.isArray(curr.name) && prevByPath.isTypeOnly === curr.isTypeOnly) {
56
+ prevByPath.name = [...new Set([...prevByPath.name, ...curr.name])]
57
+
58
+ return prev
59
+ }
60
+
61
+ return [...prev, curr]
62
+ },
63
+ [] as Array<KubbFile.Export>,
64
+ )
65
+ }
66
+
67
+ export function combineImports(imports: Array<KubbFile.Import>, exports: Array<KubbFile.Export>, source?: string): Array<KubbFile.Import> {
68
+ return orderBy(imports, [
69
+ (v) => !!Array.isArray(v.name),
70
+ (v) => !v.isTypeOnly,
71
+ (v) => v.path,
72
+ (v) => !!v.name,
73
+ (v) => (Array.isArray(v.name) ? orderBy(v.name) : v.name),
74
+ ]).reduce(
75
+ (prev, curr) => {
76
+ let name = Array.isArray(curr.name) ? [...new Set(curr.name)] : curr.name
77
+
78
+ const hasImportInSource = (importName: string) => {
79
+ if (!source) {
80
+ return true
81
+ }
82
+
83
+ const checker = (name?: string) => {
84
+ return name && source.includes(name)
85
+ }
86
+
87
+ return checker(importName) || exports.some(({ name }) => (Array.isArray(name) ? name.some(checker) : checker(name)))
88
+ }
89
+
90
+ if (curr.path === curr.root) {
91
+ // root and path are the same file, remove the "./" import
92
+ return prev
93
+ }
94
+
95
+ // merge all names and check if the importName is being used in the generated source and if not filter those imports out
96
+ if (Array.isArray(name)) {
97
+ name = name.filter((item) => (typeof item === 'string' ? hasImportInSource(item) : hasImportInSource(item.propertyName)))
98
+ }
99
+
100
+ const prevByPath = prev.findLast((imp) => imp.path === curr.path && imp.isTypeOnly === curr.isTypeOnly)
101
+ const uniquePrev = prev.findLast((imp) => imp.path === curr.path && isDeepEqual(imp.name, name) && imp.isTypeOnly === curr.isTypeOnly)
102
+ const prevByPathNameAndIsTypeOnly = prev.findLast((imp) => imp.path === curr.path && isDeepEqual(imp.name, name) && imp.isTypeOnly)
103
+
104
+ if (prevByPathNameAndIsTypeOnly) {
105
+ // we already have an export that has the same path but uses `isTypeOnly` (import type ...)
106
+ return prev
107
+ }
108
+
109
+ // already unique enough or name is empty
110
+ if (uniquePrev || (Array.isArray(name) && !name.length)) {
111
+ return prev
112
+ }
113
+
114
+ // new item, append name
115
+ if (!prevByPath) {
116
+ return [
117
+ ...prev,
118
+ {
119
+ ...curr,
120
+ name,
121
+ },
122
+ ]
123
+ }
124
+
125
+ // merge all names when prev and current both have the same isTypeOnly set
126
+ if (prevByPath && Array.isArray(prevByPath.name) && Array.isArray(name) && prevByPath.isTypeOnly === curr.isTypeOnly) {
127
+ prevByPath.name = [...new Set([...prevByPath.name, ...name])]
128
+
129
+ return prev
130
+ }
131
+
132
+ // no import was found in the source, ignore import
133
+ if (!Array.isArray(name) && name && !hasImportInSource(name)) {
134
+ return prev
135
+ }
136
+
137
+ return [...prev, curr]
138
+ },
139
+ [] as Array<KubbFile.Import>,
140
+ )
141
+ }
142
+
143
+ /**
144
+ * Helper to create a file with name and id set
145
+ */
146
+ export function createFile<TMeta extends object = object>(file: KubbFile.File<TMeta>): KubbFile.ResolvedFile<TMeta> {
147
+ const extname = path.extname(file.baseName) as KubbFile.Extname
148
+ if (!extname) {
149
+ throw new Error(`No extname found for ${file.baseName}`)
150
+ }
151
+
152
+ const source = file.sources.map((item) => item.value).join('\n\n')
153
+ const exports = file.exports?.length ? combineExports(file.exports) : []
154
+ const imports = file.imports?.length && source ? combineImports(file.imports, exports, source) : []
155
+ const sources = file.sources?.length ? combineSources(file.sources) : []
156
+
157
+ return {
158
+ ...file,
159
+ id: hashObject({ path: file.path }),
160
+ name: trimExtName(file.baseName),
161
+ extname,
162
+ imports: imports,
163
+ exports: exports,
164
+ sources: sources,
165
+ meta: file.meta || ({} as TMeta),
166
+ }
167
+ }