@kubb/fabric-core 0.1.0 → 0.1.2
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/App-DZuROf6f.d.ts +292 -0
- package/dist/App-zyf9KG3p.d.cts +292 -0
- package/dist/chunk-CUT6urMc.cjs +30 -0
- package/dist/defineApp-D3B0bU-z.d.cts +14 -0
- package/dist/defineApp-DJVMk9lc.d.ts +14 -0
- package/dist/index.cjs +217 -73
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +10 -5
- package/dist/index.d.ts +10 -5
- package/dist/index.js +204 -63
- package/dist/index.js.map +1 -1
- package/dist/parsers/typescript.cjs +5 -5
- package/dist/parsers/typescript.d.cts +3 -51
- package/dist/parsers/typescript.d.ts +3 -51
- package/dist/parsers/typescript.js +2 -2
- package/dist/parsers.cjs +7 -0
- package/dist/parsers.d.cts +14 -0
- package/dist/parsers.d.ts +14 -0
- package/dist/parsers.js +4 -0
- package/dist/plugins.cjs +76 -0
- package/dist/plugins.cjs.map +1 -0
- package/dist/plugins.d.cts +28 -0
- package/dist/plugins.d.ts +28 -0
- package/dist/plugins.js +71 -0
- package/dist/plugins.js.map +1 -0
- package/dist/tsxParser-C741ZKCN.js +26 -0
- package/dist/tsxParser-C741ZKCN.js.map +1 -0
- package/dist/tsxParser-HDf_3TMc.cjs +37 -0
- package/dist/tsxParser-HDf_3TMc.cjs.map +1 -0
- package/dist/types.d.cts +2 -2
- package/dist/types.d.ts +2 -2
- package/dist/{parser-CWB_OBtr.js → typescriptParser-BBGeFKlP.js} +51 -98
- package/dist/typescriptParser-BBGeFKlP.js.map +1 -0
- package/dist/typescriptParser-BBbbmG5W.cjs +171 -0
- package/dist/typescriptParser-BBbbmG5W.cjs.map +1 -0
- package/dist/typescriptParser-C-sBy1iR.d.cts +50 -0
- package/dist/typescriptParser-CtMmz0UV.d.ts +50 -0
- package/package.json +13 -6
- package/src/App.ts +91 -0
- package/src/FileManager.ts +14 -193
- package/src/FileProcessor.ts +89 -0
- package/src/createFile.ts +167 -0
- package/src/defineApp.ts +49 -74
- package/src/index.ts +3 -1
- package/src/parsers/createParser.ts +8 -0
- package/src/parsers/defaultParser.ts +10 -0
- package/src/parsers/index.ts +5 -0
- package/src/parsers/tsxParser.ts +11 -0
- package/src/parsers/types.ts +22 -0
- package/src/parsers/{typescript.ts → typescriptParser.ts} +8 -4
- package/src/plugins/createPlugin.ts +10 -0
- package/src/plugins/fsPlugin.ts +112 -0
- package/src/plugins/index.ts +3 -0
- package/src/plugins/types.ts +15 -0
- package/src/types.ts +4 -1
- package/src/utils/AsyncEventEmitter.ts +37 -0
- package/src/utils/EventEmitter.ts +23 -0
- package/src/utils/getRelativePath.ts +32 -0
- package/src/utils/trimExtName.ts +3 -0
- package/dist/KubbFile-BrN7Wwp6.d.cts +0 -119
- package/dist/KubbFile-BzVkcu9M.d.ts +0 -119
- package/dist/defineApp-Bg7JewJQ.d.ts +0 -62
- package/dist/defineApp-DKW3IRO8.d.cts +0 -62
- package/dist/parser-CWB_OBtr.js.map +0 -1
- package/dist/parser-D64DdV1v.d.cts +0 -21
- package/dist/parser-QF8j8-pj.cjs +0 -260
- package/dist/parser-QF8j8-pj.cjs.map +0 -1
- package/dist/parser-yYqnryUV.d.ts +0 -21
- package/dist/parsers/tsx.cjs +0 -3
- package/dist/parsers/tsx.d.cts +0 -8
- package/dist/parsers/tsx.d.ts +0 -8
- package/dist/parsers/tsx.js +0 -3
- package/src/fs.ts +0 -167
- package/src/parsers/parser.ts +0 -56
- package/src/parsers/tsx.ts +0 -8
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import type * as KubbFile from './KubbFile.ts'
|
|
2
|
+
import pLimit from 'p-limit'
|
|
3
|
+
import path from 'node:path'
|
|
4
|
+
|
|
5
|
+
import type { Parser } from './parsers/types.ts'
|
|
6
|
+
import { defaultParser } from './parsers/defaultParser.ts'
|
|
7
|
+
import { AsyncEventEmitter } from './utils/AsyncEventEmitter.ts'
|
|
8
|
+
import type { AppEvents } from './App.ts'
|
|
9
|
+
import { typescriptParser } from './parsers/typescriptParser.ts'
|
|
10
|
+
import { tsxParser } from './parsers/tsxParser.ts'
|
|
11
|
+
|
|
12
|
+
export type ProcessFilesProps = {
|
|
13
|
+
parsers?: Set<Parser>
|
|
14
|
+
extension?: Record<KubbFile.Extname, KubbFile.Extname | ''>
|
|
15
|
+
dryRun?: boolean
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
type GetParseOptions = {
|
|
19
|
+
parsers?: Set<Parser>
|
|
20
|
+
extname?: KubbFile.Extname
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
type Options = {
|
|
24
|
+
events?: AsyncEventEmitter<AppEvents>
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export class FileProcessor {
|
|
28
|
+
#limit = pLimit(100)
|
|
29
|
+
events: AsyncEventEmitter<AppEvents>
|
|
30
|
+
|
|
31
|
+
constructor({ events = new AsyncEventEmitter<AppEvents>() }: Options = {}) {
|
|
32
|
+
this.events = events
|
|
33
|
+
|
|
34
|
+
return this
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
get #defaultParser(): Set<Parser> {
|
|
38
|
+
console.warn(`[parser] using default parsers, please consider using the "use" method to add custom parsers.`)
|
|
39
|
+
|
|
40
|
+
return new Set<Parser>([typescriptParser, tsxParser, defaultParser])
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
async parse(file: KubbFile.ResolvedFile, { parsers = this.#defaultParser, extname }: GetParseOptions = {}): Promise<string> {
|
|
44
|
+
if (!extname) {
|
|
45
|
+
console.warn('[parser] No extname found, default parser will be used')
|
|
46
|
+
return defaultParser.parse(file, { extname })
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const parser = [...parsers].find((item) => item.extNames?.includes(extname))
|
|
50
|
+
|
|
51
|
+
if (!parser) {
|
|
52
|
+
console.warn(`[parser] No parser found for ${extname}, default parser will be used`)
|
|
53
|
+
|
|
54
|
+
return defaultParser.parse(file, { extname })
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return parser.parse(file, { extname })
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
async run(files: Array<KubbFile.ResolvedFile>, { parsers, dryRun, extension }: ProcessFilesProps = {}): Promise<KubbFile.ResolvedFile[]> {
|
|
61
|
+
await this.events.emit('process:start', { files })
|
|
62
|
+
|
|
63
|
+
let processed = 0
|
|
64
|
+
const total = files.length
|
|
65
|
+
|
|
66
|
+
const promises = files.map((resolvedFile, index) =>
|
|
67
|
+
this.#limit(async () => {
|
|
68
|
+
const extname = extension?.[resolvedFile.extname] || (path.extname(resolvedFile.path) as KubbFile.Extname)
|
|
69
|
+
|
|
70
|
+
await this.events.emit('file:start', { file: resolvedFile, index, total })
|
|
71
|
+
|
|
72
|
+
if (!dryRun) {
|
|
73
|
+
const source = await this.parse(resolvedFile, { extname, parsers })
|
|
74
|
+
await this.events.emit('process:progress', { file: resolvedFile, source, processed, percentage: (processed / total) * 100, total })
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
await this.events.emit('file:end', { file: resolvedFile, index, total })
|
|
78
|
+
|
|
79
|
+
processed++
|
|
80
|
+
}),
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
await Promise.all(promises)
|
|
84
|
+
|
|
85
|
+
await this.events.emit('process:end', { files })
|
|
86
|
+
|
|
87
|
+
return files
|
|
88
|
+
}
|
|
89
|
+
}
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import type * as KubbFile from './KubbFile.ts'
|
|
2
|
+
import { trimExtName } from './utils/trimExtName.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,17 +1,9 @@
|
|
|
1
|
-
import type * as KubbFile from './KubbFile.ts'
|
|
2
1
|
import { FileManager } from './FileManager.ts'
|
|
3
|
-
import { isPromise } from 'remeda'
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
type Component
|
|
8
|
-
|
|
9
|
-
type PluginInstallFunction<Options = any[]> = Options extends unknown[] ? (app: App, ...options: Options) => any : (app: App, options: Options) => any
|
|
10
|
-
|
|
11
|
-
export type ObjectPlugin<Options = any[]> = {
|
|
12
|
-
install: PluginInstallFunction<Options>
|
|
13
|
-
}
|
|
14
|
-
export type FunctionPlugin<Options = any[]> = PluginInstallFunction<Options> & Partial<ObjectPlugin<Options>>
|
|
2
|
+
import { isFunction, isPromise } from 'remeda'
|
|
3
|
+
import type { Plugin } from './plugins/types.ts'
|
|
4
|
+
import type { Parser } from './parsers/types.ts'
|
|
5
|
+
import { AsyncEventEmitter } from './utils/AsyncEventEmitter.ts'
|
|
6
|
+
import type { App, AppContext, Component, AppEvents } from './App.ts'
|
|
15
7
|
|
|
16
8
|
type AppRenderer = {
|
|
17
9
|
render(): Promise<void> | void
|
|
@@ -19,57 +11,27 @@ type AppRenderer = {
|
|
|
19
11
|
waitUntilExit(): Promise<void>
|
|
20
12
|
}
|
|
21
13
|
|
|
22
|
-
export type AppContext<TOptions = unknown> = {
|
|
23
|
-
options?: TOptions
|
|
24
|
-
fileManager: FileManager
|
|
25
|
-
addFile(...files: Array<KubbFile.File>): Promise<void>
|
|
26
|
-
files: Array<KubbFile.ResolvedFile>
|
|
27
|
-
clear: () => void
|
|
28
|
-
}
|
|
29
|
-
|
|
30
14
|
type RootRenderFunction<THostElement, TContext extends AppContext> = (this: TContext, container: THostElement, context: TContext) => AppRenderer
|
|
31
15
|
|
|
32
|
-
type Plugin<Options = any[], P extends unknown[] = Options extends unknown[] ? Options : [Options]> = FunctionPlugin<P> | ObjectPlugin<P>
|
|
33
|
-
|
|
34
|
-
type WriteOptions = {
|
|
35
|
-
extension?: Record<KubbFile.Extname, KubbFile.Extname | ''>
|
|
36
|
-
dryRun?: boolean
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
export interface App {
|
|
40
|
-
_component: Component
|
|
41
|
-
render(): Promise<void>
|
|
42
|
-
renderToString(): Promise<string>
|
|
43
|
-
getFiles(): Promise<Array<KubbFile.ResolvedFile>>
|
|
44
|
-
use<Options>(plugin: Plugin<Options>, options: NoInfer<Options>): this
|
|
45
|
-
write(options?: WriteOptions): Promise<void>
|
|
46
|
-
addFile(...files: Array<KubbFile.File>): Promise<void>
|
|
47
|
-
waitUntilExit(): Promise<void>
|
|
48
|
-
}
|
|
49
|
-
|
|
50
16
|
export type DefineApp<TContext extends AppContext> = (rootComponent?: Component, options?: TContext['options']) => App
|
|
51
17
|
|
|
52
18
|
export function defineApp<THostElement, TContext extends AppContext>(instance: RootRenderFunction<THostElement, TContext>): DefineApp<TContext> {
|
|
53
19
|
function createApp(rootComponent: Component, options?: TContext['options']): App {
|
|
54
|
-
const
|
|
55
|
-
const
|
|
20
|
+
const events = new AsyncEventEmitter<AppEvents>()
|
|
21
|
+
const installedPlugins = new Set<Plugin>()
|
|
22
|
+
const installedParsers = new Set<Parser>()
|
|
23
|
+
const fileManager = new FileManager({ events })
|
|
56
24
|
const context = {
|
|
25
|
+
events,
|
|
57
26
|
options,
|
|
58
27
|
fileManager,
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
},
|
|
62
|
-
clear() {
|
|
63
|
-
context.fileManager.clear()
|
|
64
|
-
},
|
|
65
|
-
get files() {
|
|
66
|
-
return fileManager.getFiles()
|
|
67
|
-
},
|
|
28
|
+
installedPlugins,
|
|
29
|
+
installedParsers,
|
|
68
30
|
} as TContext
|
|
69
31
|
|
|
70
32
|
const { render, renderToString, waitUntilExit } = instance.call(context, rootComponent, context)
|
|
71
33
|
|
|
72
|
-
const app
|
|
34
|
+
const app = {
|
|
73
35
|
_component: rootComponent,
|
|
74
36
|
async render() {
|
|
75
37
|
if (isPromise(render)) {
|
|
@@ -81,36 +43,49 @@ export function defineApp<THostElement, TContext extends AppContext>(instance: R
|
|
|
81
43
|
async renderToString() {
|
|
82
44
|
return renderToString()
|
|
83
45
|
},
|
|
84
|
-
|
|
85
|
-
return fileManager.
|
|
46
|
+
get files() {
|
|
47
|
+
return fileManager.files
|
|
86
48
|
},
|
|
87
49
|
waitUntilExit,
|
|
88
|
-
addFile
|
|
89
|
-
|
|
90
|
-
options = {
|
|
91
|
-
extension: { '.ts': '.ts' },
|
|
92
|
-
dryRun: false,
|
|
93
|
-
},
|
|
94
|
-
) {
|
|
95
|
-
await fileManager.processFiles({
|
|
96
|
-
extension: options.extension,
|
|
97
|
-
dryRun: options.dryRun,
|
|
98
|
-
})
|
|
50
|
+
async addFile(...newFiles) {
|
|
51
|
+
await fileManager.add(...newFiles)
|
|
99
52
|
},
|
|
100
|
-
use(
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
installedPlugins.
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
53
|
+
use(pluginOrParser, ...options) {
|
|
54
|
+
const args = Array.isArray(options) ? options : [options[0]]
|
|
55
|
+
|
|
56
|
+
if (pluginOrParser.type === 'plugin') {
|
|
57
|
+
if (installedPlugins.has(pluginOrParser)) {
|
|
58
|
+
console.warn('Plugin has already been applied to target app.')
|
|
59
|
+
} else {
|
|
60
|
+
installedPlugins.add(pluginOrParser)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (pluginOrParser.override && isFunction(pluginOrParser.override)) {
|
|
64
|
+
const overrider = pluginOrParser.override
|
|
65
|
+
|
|
66
|
+
const extraApp = (overrider as any)(app, context, ...args)
|
|
67
|
+
Object.assign(app, extraApp)
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
if (pluginOrParser.type === 'parser') {
|
|
71
|
+
if (installedParsers.has(pluginOrParser)) {
|
|
72
|
+
console.warn('Parser has already been applied to target app.')
|
|
73
|
+
} else {
|
|
74
|
+
installedParsers.add(pluginOrParser)
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (pluginOrParser && isFunction(pluginOrParser.install)) {
|
|
79
|
+
const installer = pluginOrParser.install
|
|
80
|
+
|
|
81
|
+
;(installer as any)(app, context, ...args)
|
|
109
82
|
}
|
|
110
83
|
|
|
111
84
|
return app
|
|
112
85
|
},
|
|
113
|
-
}
|
|
86
|
+
} as App
|
|
87
|
+
|
|
88
|
+
events.emit('start', { app })
|
|
114
89
|
|
|
115
90
|
return app
|
|
116
91
|
}
|
package/src/index.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
export { createApp } from './createApp.ts'
|
|
2
2
|
export { defineApp } from './defineApp.ts'
|
|
3
3
|
export { FileManager } from './FileManager.ts'
|
|
4
|
-
export {
|
|
4
|
+
export { createFile } from './createFile.ts'
|
|
5
|
+
export { FileProcessor } from './FileProcessor.ts'
|
|
6
|
+
export type { App } from './App.ts'
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { typescriptParser } from './typescriptParser.ts'
|
|
2
|
+
import { createParser } from './createParser.ts'
|
|
3
|
+
|
|
4
|
+
export const tsxParser = createParser({
|
|
5
|
+
name: 'tsx',
|
|
6
|
+
extNames: ['.tsx', '.jsx'],
|
|
7
|
+
install() {},
|
|
8
|
+
async parse(file, options = { extname: '.tsx' }) {
|
|
9
|
+
return typescriptParser.parse(file, options)
|
|
10
|
+
},
|
|
11
|
+
})
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type * as KubbFile from '../KubbFile.ts'
|
|
2
|
+
import type { Install } from '../App.ts'
|
|
3
|
+
|
|
4
|
+
type PrintOptions = {
|
|
5
|
+
extname?: KubbFile.Extname
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export type Parser<TOptions = any[], TMeta extends object = any> = {
|
|
9
|
+
name: string
|
|
10
|
+
type: 'parser'
|
|
11
|
+
/**
|
|
12
|
+
* Undefined is being used for the defaultParser
|
|
13
|
+
*/
|
|
14
|
+
extNames: Array<KubbFile.Extname> | undefined
|
|
15
|
+
install: Install<TOptions>
|
|
16
|
+
/**
|
|
17
|
+
* Convert a file to string
|
|
18
|
+
*/
|
|
19
|
+
parse(file: KubbFile.ResolvedFile<TMeta>, options: PrintOptions): Promise<string>
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export type UserParser<TOptions = any[], TMeta extends object = any> = Omit<Parser<TOptions, TMeta>, 'type'>
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import ts from 'typescript'
|
|
2
|
-
import { getRelativePath
|
|
2
|
+
import { getRelativePath } from '../utils/getRelativePath.ts'
|
|
3
|
+
import { trimExtName } from '../utils/trimExtName.ts'
|
|
3
4
|
import path from 'node:path'
|
|
4
|
-
import {
|
|
5
|
+
import { createParser } from './createParser.ts'
|
|
5
6
|
|
|
6
7
|
const { factory } = ts
|
|
7
8
|
|
|
@@ -147,8 +148,11 @@ export function createExport({
|
|
|
147
148
|
)
|
|
148
149
|
}
|
|
149
150
|
|
|
150
|
-
export const
|
|
151
|
-
|
|
151
|
+
export const typescriptParser = createParser({
|
|
152
|
+
name: 'typescript',
|
|
153
|
+
extNames: ['.ts', '.js'],
|
|
154
|
+
install() {},
|
|
155
|
+
async parse(file, options = { extname: '.ts' }) {
|
|
152
156
|
const source = file.sources.map((item) => item.value).join('\n\n')
|
|
153
157
|
|
|
154
158
|
const importNodes = file.imports
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { Plugin, UserPlugin } from './types.ts'
|
|
2
|
+
|
|
3
|
+
export function createPlugin<Options = any[], TAppExtension extends Record<string, any> = {}>(
|
|
4
|
+
plugin: UserPlugin<Options, TAppExtension>,
|
|
5
|
+
): Plugin<Options, TAppExtension> {
|
|
6
|
+
return {
|
|
7
|
+
type: 'plugin',
|
|
8
|
+
...plugin,
|
|
9
|
+
}
|
|
10
|
+
}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { createPlugin } from './createPlugin.ts'
|
|
2
|
+
import { switcher } from 'js-runtime'
|
|
3
|
+
import fs from 'fs-extra'
|
|
4
|
+
import { resolve } from 'node:path'
|
|
5
|
+
import type * as KubbFile from '../KubbFile.ts'
|
|
6
|
+
|
|
7
|
+
type Options = {
|
|
8
|
+
/**
|
|
9
|
+
* Optional callback that is invoked whenever a file is written by the plugin.
|
|
10
|
+
* Useful for tests to observe write operations without spying on internal functions.
|
|
11
|
+
*/
|
|
12
|
+
onWrite?: (path: string, data: string) => void | Promise<void>
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export async function write(path: string, data: string, options: { sanity?: boolean } = {}): Promise<string | undefined> {
|
|
16
|
+
if (data.trim() === '') {
|
|
17
|
+
return undefined
|
|
18
|
+
}
|
|
19
|
+
return switcher(
|
|
20
|
+
{
|
|
21
|
+
node: async (path: string, data: string, { sanity }: { sanity?: boolean }) => {
|
|
22
|
+
try {
|
|
23
|
+
const oldContent = await fs.readFile(resolve(path), {
|
|
24
|
+
encoding: 'utf-8',
|
|
25
|
+
})
|
|
26
|
+
if (oldContent?.toString() === data?.toString()) {
|
|
27
|
+
return
|
|
28
|
+
}
|
|
29
|
+
} catch (_err) {
|
|
30
|
+
/* empty */
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
await fs.outputFile(resolve(path), data, { encoding: 'utf-8' })
|
|
34
|
+
|
|
35
|
+
if (sanity) {
|
|
36
|
+
const savedData = await fs.readFile(resolve(path), {
|
|
37
|
+
encoding: 'utf-8',
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
if (savedData?.toString() !== data?.toString()) {
|
|
41
|
+
throw new Error(`Sanity check failed for ${path}\n\nData[${data.length}]:\n${data}\n\nSaved[${savedData.length}]:\n${savedData}\n`)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return savedData
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return data
|
|
48
|
+
},
|
|
49
|
+
bun: async (path: string, data: string, { sanity }: { sanity?: boolean }) => {
|
|
50
|
+
try {
|
|
51
|
+
await Bun.write(resolve(path), data)
|
|
52
|
+
|
|
53
|
+
if (sanity) {
|
|
54
|
+
const file = Bun.file(resolve(path))
|
|
55
|
+
const savedData = await file.text()
|
|
56
|
+
|
|
57
|
+
if (savedData?.toString() !== data?.toString()) {
|
|
58
|
+
throw new Error(`Sanity check failed for ${path}\n\nData[${path.length}]:\n${path}\n\nSaved[${savedData.length}]:\n${savedData}\n`)
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return savedData
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return data
|
|
65
|
+
} catch (e) {
|
|
66
|
+
console.error(e)
|
|
67
|
+
}
|
|
68
|
+
},
|
|
69
|
+
},
|
|
70
|
+
'node',
|
|
71
|
+
)(path, data.trim(), options)
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
type WriteOptions = {
|
|
75
|
+
extension?: Record<KubbFile.Extname, KubbFile.Extname | ''>
|
|
76
|
+
dryRun?: boolean
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
declare module '../index.ts' {
|
|
80
|
+
interface App {
|
|
81
|
+
write(options?: WriteOptions): Promise<void>
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export const fsPlugin = createPlugin<Options, { write(options?: WriteOptions): Promise<void> }>({
|
|
86
|
+
name: 'fs',
|
|
87
|
+
scope: 'write',
|
|
88
|
+
async install(_app, context, options) {
|
|
89
|
+
context.events.on('process:progress', async ({ file, source }) => {
|
|
90
|
+
if (options?.onWrite) {
|
|
91
|
+
await options.onWrite(file.path, source)
|
|
92
|
+
}
|
|
93
|
+
await write(file.path, source, { sanity: false })
|
|
94
|
+
})
|
|
95
|
+
},
|
|
96
|
+
override(_app, context) {
|
|
97
|
+
return {
|
|
98
|
+
async write(
|
|
99
|
+
options = {
|
|
100
|
+
extension: { '.ts': '.ts' },
|
|
101
|
+
dryRun: false,
|
|
102
|
+
},
|
|
103
|
+
) {
|
|
104
|
+
await context.fileManager.write({
|
|
105
|
+
extension: options.extension,
|
|
106
|
+
dryRun: options.dryRun,
|
|
107
|
+
parsers: context.installedParsers,
|
|
108
|
+
})
|
|
109
|
+
},
|
|
110
|
+
}
|
|
111
|
+
},
|
|
112
|
+
})
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { Install, Override } from '../App.ts'
|
|
2
|
+
|
|
3
|
+
export type Plugin<TOptions = any[], TAppExtension extends Record<string, any> = {}> = {
|
|
4
|
+
name: string
|
|
5
|
+
type: 'plugin'
|
|
6
|
+
scope?: 'write' | 'read' | (string & {})
|
|
7
|
+
install: Install<TOptions> | Promise<Install<TOptions>>
|
|
8
|
+
/**
|
|
9
|
+
* Runtime app overrides or extensions.
|
|
10
|
+
* Merged into the app instance after install.
|
|
11
|
+
*/
|
|
12
|
+
override?: Override<TOptions, TAppExtension>
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export type UserPlugin<TOptions = any[], TAppExtension extends Record<string, any> = {}> = Omit<Plugin<TOptions, TAppExtension>, 'type'>
|
package/src/types.ts
CHANGED