@kubb/fabric-core 0.0.0-canary-20251021083045 → 0.0.0-canary-20251021135548
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/{types-lS0JaZqX.d.cts → KubbFile-BrN7Wwp6.d.cts} +3 -3
- package/dist/{types-BY5X8xoR.d.ts → KubbFile-BzVkcu9M.d.ts} +3 -3
- package/dist/createFileParser-BD8yn0LT.cjs +14 -0
- package/dist/createFileParser-BD8yn0LT.cjs.map +1 -0
- package/dist/createFileParser-Cix3AMLd.js +8 -0
- package/dist/createFileParser-Cix3AMLd.js.map +1 -0
- package/dist/default-DCpuPmrL.js +10 -0
- package/dist/default-DCpuPmrL.js.map +1 -0
- package/dist/default-DNBu_jsL.cjs +15 -0
- package/dist/default-DNBu_jsL.cjs.map +1 -0
- package/dist/defineApp-BX0gsgY3.d.cts +54 -0
- package/dist/defineApp-DygRR8LS.d.ts +54 -0
- package/dist/index.cjs +138 -87
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +44 -53
- package/dist/index.d.ts +44 -53
- package/dist/index.js +128 -76
- package/dist/index.js.map +1 -1
- package/dist/parsers/default.cjs +4 -0
- package/dist/parsers/default.d.cts +8 -0
- package/dist/parsers/default.d.ts +8 -0
- package/dist/parsers/default.js +4 -0
- package/dist/parsers/tsx.cjs +4 -2
- package/dist/parsers/tsx.d.cts +3 -3
- package/dist/parsers/tsx.d.ts +3 -3
- package/dist/parsers/tsx.js +3 -1
- package/dist/parsers/typescript.cjs +6 -5
- package/dist/parsers/typescript.d.cts +3 -3
- package/dist/parsers/typescript.d.ts +3 -3
- package/dist/parsers/typescript.js +2 -1
- package/dist/tsx-BSUaIML3.cjs +16 -0
- package/dist/tsx-BSUaIML3.cjs.map +1 -0
- package/dist/tsx-DBAk9dqS.js +11 -0
- package/dist/tsx-DBAk9dqS.js.map +1 -0
- package/dist/types-CkbelZaS.d.ts +15 -0
- package/dist/types-GueHciQ3.d.cts +15 -0
- package/dist/types.cjs +12 -0
- package/dist/types.cjs.map +1 -0
- package/dist/types.d.cts +3 -0
- package/dist/types.d.ts +3 -0
- package/dist/types.js +6 -0
- package/dist/types.js.map +1 -0
- package/dist/{parser-CWB_OBtr.js → typescript-C60gWBu8.js} +4 -34
- package/dist/typescript-C60gWBu8.js.map +1 -0
- package/dist/{parser-QF8j8-pj.cjs → typescript-Z90jN87k.cjs} +5 -47
- package/dist/typescript-Z90jN87k.cjs.map +1 -0
- package/package.json +15 -1
- package/src/FileManager.ts +11 -203
- package/src/FileProcessor.ts +86 -0
- package/src/KubbFile.ts +132 -0
- package/src/createFile.ts +167 -0
- package/src/defineApp.ts +8 -6
- package/src/index.ts +4 -2
- package/src/parsers/createFileParser.ts +5 -0
- package/src/parsers/default.ts +7 -0
- package/src/parsers/tsx.ts +1 -1
- package/src/parsers/types.ts +12 -0
- package/src/parsers/typescript.ts +1 -1
- package/src/types.ts +2 -132
- package/src/utils/EventEmitter.ts +23 -0
- package/dist/parser-Bck6QDwN.d.cts +0 -15
- package/dist/parser-CRl-iUw1.d.ts +0 -15
- package/dist/parser-CWB_OBtr.js.map +0 -1
- package/dist/parser-QF8j8-pj.cjs.map +0 -1
- package/src/parsers/parser.ts +0 -56
package/src/FileManager.ts
CHANGED
|
@@ -1,22 +1,20 @@
|
|
|
1
|
-
import
|
|
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
|
|
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 {
|
|
5
|
+
import { createFile } from './createFile.ts'
|
|
11
6
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
7
|
+
function mergeFile<TMeta extends object = object>(a: KubbFile.File<TMeta>, b: KubbFile.File<TMeta>): KubbFile.File<TMeta> {
|
|
8
|
+
return {
|
|
9
|
+
...a,
|
|
10
|
+
sources: [...(a.sources || []), ...(b.sources || [])],
|
|
11
|
+
imports: [...(a.imports || []), ...(b.imports || [])],
|
|
12
|
+
exports: [...(a.exports || []), ...(b.exports || [])],
|
|
13
|
+
}
|
|
15
14
|
}
|
|
16
15
|
|
|
17
16
|
export class FileManager {
|
|
18
17
|
#cache = new Cache<KubbFile.ResolvedFile>()
|
|
19
|
-
#limit = pLimit(100)
|
|
20
18
|
|
|
21
19
|
constructor() {
|
|
22
20
|
return this
|
|
@@ -67,7 +65,7 @@ export class FileManager {
|
|
|
67
65
|
this.#cache.clear()
|
|
68
66
|
}
|
|
69
67
|
|
|
70
|
-
|
|
68
|
+
get files(): Array<KubbFile.ResolvedFile> {
|
|
71
69
|
const cachedKeys = this.#cache.keys()
|
|
72
70
|
|
|
73
71
|
// order by path length and if file is a barrel file
|
|
@@ -77,194 +75,4 @@ export class FileManager {
|
|
|
77
75
|
|
|
78
76
|
return files.filter(Boolean)
|
|
79
77
|
}
|
|
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) : []
|
|
259
|
-
|
|
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),
|
|
269
|
-
}
|
|
270
78
|
}
|
|
@@ -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
|
+
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
|
+
}
|
package/src/KubbFile.ts
ADDED
|
@@ -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
|
+
}
|
package/src/defineApp.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import type * as KubbFile from './
|
|
1
|
+
import type * as KubbFile from './KubbFile.ts'
|
|
2
2
|
import { FileManager } from './FileManager.ts'
|
|
3
3
|
import { isPromise } from 'remeda'
|
|
4
|
+
import { FileProcessor } from './FileProcessor.ts'
|
|
4
5
|
|
|
5
6
|
const isFunction = (val: unknown): val is Function => typeof val === 'function'
|
|
6
7
|
|
|
@@ -40,7 +41,7 @@ export interface App {
|
|
|
40
41
|
_component: Component
|
|
41
42
|
render(): Promise<void>
|
|
42
43
|
renderToString(): Promise<string>
|
|
43
|
-
|
|
44
|
+
files: Array<KubbFile.ResolvedFile>
|
|
44
45
|
use<Options>(plugin: Plugin<Options>, options: NoInfer<Options>): this
|
|
45
46
|
write(options?: WriteOptions): Promise<void>
|
|
46
47
|
addFile(...files: Array<KubbFile.File>): Promise<void>
|
|
@@ -53,6 +54,7 @@ export function defineApp<THostElement, TContext extends AppContext>(instance: R
|
|
|
53
54
|
function createApp(rootComponent: Component, options?: TContext['options']): App {
|
|
54
55
|
const installedPlugins = new WeakSet()
|
|
55
56
|
const fileManager = new FileManager()
|
|
57
|
+
const fileProcessor = new FileProcessor()
|
|
56
58
|
const context = {
|
|
57
59
|
options,
|
|
58
60
|
fileManager,
|
|
@@ -63,7 +65,7 @@ export function defineApp<THostElement, TContext extends AppContext>(instance: R
|
|
|
63
65
|
context.fileManager.clear()
|
|
64
66
|
},
|
|
65
67
|
get files() {
|
|
66
|
-
return fileManager.
|
|
68
|
+
return fileManager.files
|
|
67
69
|
},
|
|
68
70
|
} as TContext
|
|
69
71
|
|
|
@@ -81,8 +83,8 @@ export function defineApp<THostElement, TContext extends AppContext>(instance: R
|
|
|
81
83
|
async renderToString() {
|
|
82
84
|
return renderToString()
|
|
83
85
|
},
|
|
84
|
-
|
|
85
|
-
return fileManager.
|
|
86
|
+
get files() {
|
|
87
|
+
return fileManager.files
|
|
86
88
|
},
|
|
87
89
|
waitUntilExit,
|
|
88
90
|
addFile: context.addFile,
|
|
@@ -92,7 +94,7 @@ export function defineApp<THostElement, TContext extends AppContext>(instance: R
|
|
|
92
94
|
dryRun: false,
|
|
93
95
|
},
|
|
94
96
|
) {
|
|
95
|
-
await fileManager.
|
|
97
|
+
await fileProcessor.run(fileManager.files, {
|
|
96
98
|
extension: options.extension,
|
|
97
99
|
dryRun: options.dryRun,
|
|
98
100
|
})
|
package/src/index.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
export { createApp } from './createApp.ts'
|
|
2
|
-
export type { DefineApp, AppContext } from './defineApp.ts'
|
|
3
2
|
export { defineApp } from './defineApp.ts'
|
|
4
|
-
export
|
|
3
|
+
export { FileManager } from './FileManager.ts'
|
|
4
|
+
export { createFile } from './createFile.ts'
|
|
5
|
+
export { FileProcessor } from './FileProcessor.ts'
|
|
6
|
+
export { createFileParser } from './parsers/createFileParser.ts'
|