@kubb/fabric-core 0.1.5 → 0.1.7

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 (76) hide show
  1. package/dist/{App-9ie0H1SF.d.ts → App-DVWD6TgC.d.cts} +28 -17
  2. package/dist/{App-KqAHuAyU.d.cts → App-_vPNh477.d.ts} +28 -17
  3. package/dist/createParser-17uGjfu3.js +11 -0
  4. package/dist/createParser-17uGjfu3.js.map +1 -0
  5. package/dist/createParser-C4IkyTs5.cjs +17 -0
  6. package/dist/createParser-C4IkyTs5.cjs.map +1 -0
  7. package/dist/{defaultParser-vwyTb1XT.js → defaultParser--HzU9LPa.js} +2 -2
  8. package/dist/{defaultParser-vwyTb1XT.js.map → defaultParser--HzU9LPa.js.map} +1 -1
  9. package/dist/{defaultParser-Dl-OrbH1.cjs → defaultParser-n9VW2iVf.cjs} +2 -2
  10. package/dist/{defaultParser-Dl-OrbH1.cjs.map → defaultParser-n9VW2iVf.cjs.map} +1 -1
  11. package/dist/defineProperty-BtekiGIK.js +332 -0
  12. package/dist/defineProperty-BtekiGIK.js.map +1 -0
  13. package/dist/defineProperty-CspRhtP3.cjs +364 -0
  14. package/dist/defineProperty-CspRhtP3.cjs.map +1 -0
  15. package/dist/getRelativePath-C6lvNCs7.cjs +26 -0
  16. package/dist/getRelativePath-C6lvNCs7.cjs.map +1 -0
  17. package/dist/getRelativePath-CERJmYkp.js +19 -0
  18. package/dist/getRelativePath-CERJmYkp.js.map +1 -0
  19. package/dist/{index-BpPNNyhl.d.ts → index-CfV-59_M.d.ts} +5 -5
  20. package/dist/{index-DLITiDO5.d.cts → index-DVok6g82.d.cts} +5 -5
  21. package/dist/index.cjs +81 -225
  22. package/dist/index.cjs.map +1 -1
  23. package/dist/index.d.cts +2 -2
  24. package/dist/index.d.ts +2 -2
  25. package/dist/index.js +54 -194
  26. package/dist/index.js.map +1 -1
  27. package/dist/parsers/typescript.cjs +4 -2
  28. package/dist/parsers/typescript.d.cts +2 -2
  29. package/dist/parsers/typescript.d.ts +2 -2
  30. package/dist/parsers/typescript.js +4 -2
  31. package/dist/parsers.cjs +5 -3
  32. package/dist/parsers.cjs.map +1 -1
  33. package/dist/parsers.d.cts +3 -3
  34. package/dist/parsers.d.ts +3 -3
  35. package/dist/parsers.js +5 -3
  36. package/dist/parsers.js.map +1 -1
  37. package/dist/plugins.cjs +260 -33
  38. package/dist/plugins.cjs.map +1 -1
  39. package/dist/plugins.d.cts +48 -8
  40. package/dist/plugins.d.ts +48 -8
  41. package/dist/plugins.js +249 -25
  42. package/dist/plugins.js.map +1 -1
  43. package/dist/{chunk-CUT6urMc.cjs → trimExtName-Bb4zGVF1.cjs} +14 -1
  44. package/dist/trimExtName-Bb4zGVF1.cjs.map +1 -0
  45. package/dist/trimExtName-CeOVQVbu.js +8 -0
  46. package/dist/trimExtName-CeOVQVbu.js.map +1 -0
  47. package/dist/types.d.cts +2 -2
  48. package/dist/types.d.ts +2 -2
  49. package/dist/{typescriptParser-Du4RIToQ.d.cts → typescriptParser-BM90H8Tx.d.cts} +2 -2
  50. package/dist/{typescriptParser-JawJ8wET.cjs → typescriptParser-CNHO6H2_.cjs} +10 -24
  51. package/dist/typescriptParser-CNHO6H2_.cjs.map +1 -0
  52. package/dist/{typescriptParser-CrzOv_Aw.js → typescriptParser-CWT7zCJy.js} +5 -18
  53. package/dist/typescriptParser-CWT7zCJy.js.map +1 -0
  54. package/dist/{typescriptParser-Dk1rwKyJ.d.ts → typescriptParser-Chjs-RhT.d.ts} +2 -2
  55. package/package.json +4 -2
  56. package/src/App.ts +31 -28
  57. package/src/FileManager.ts +8 -1
  58. package/src/FileProcessor.ts +48 -18
  59. package/src/defineApp.ts +5 -10
  60. package/src/parsers/createParser.ts +1 -1
  61. package/src/parsers/types.ts +2 -2
  62. package/src/plugins/barrelPlugin.ts +207 -0
  63. package/src/plugins/createPlugin.ts +1 -1
  64. package/src/plugins/fsPlugin.ts +33 -34
  65. package/src/plugins/index.ts +2 -0
  66. package/src/plugins/progressPlugin.ts +48 -0
  67. package/src/plugins/types.ts +3 -3
  68. package/src/utils/AsyncEventEmitter.ts +8 -0
  69. package/src/utils/EventEmitter.ts +8 -0
  70. package/src/utils/TreeNode.ts +118 -0
  71. package/dist/createParser-B_RpW6sx.js +0 -17
  72. package/dist/createParser-B_RpW6sx.js.map +0 -1
  73. package/dist/createParser-DZB5qExa.cjs +0 -29
  74. package/dist/createParser-DZB5qExa.cjs.map +0 -1
  75. package/dist/typescriptParser-CrzOv_Aw.js.map +0 -1
  76. package/dist/typescriptParser-JawJ8wET.cjs.map +0 -1
package/src/App.ts CHANGED
@@ -12,6 +12,13 @@ declare global {
12
12
 
13
13
  export type Component = any
14
14
 
15
+ export type AppOptions = {
16
+ /**
17
+ * @default 'sequential'
18
+ */
19
+ mode?: AppMode
20
+ }
21
+
15
22
  export type AppEvents = {
16
23
  /**
17
24
  * Called in the beginning of the app lifecycle.
@@ -49,7 +56,7 @@ export type AppEvents = {
49
56
  processed: number
50
57
  total: number
51
58
  percentage: number
52
- source: string
59
+ source?: string
53
60
  file: KubbFile.ResolvedFile
54
61
  },
55
62
  ]
@@ -60,7 +67,7 @@ export type AppEvents = {
60
67
  'process:end': [{ files: KubbFile.ResolvedFile[] }]
61
68
  }
62
69
 
63
- export type AppContext<TOptions = unknown> = {
70
+ export type AppContext<TOptions extends AppOptions> = {
64
71
  options?: TOptions
65
72
  events: AsyncEventEmitter<AppEvents>
66
73
  fileManager: FileManager
@@ -68,36 +75,32 @@ export type AppContext<TOptions = unknown> = {
68
75
  installedParsers: Set<Parser>
69
76
  }
70
77
 
71
- export type Install<TOptions = any[] | object | undefined> = TOptions extends any[]
72
- ? (app: App, ...options: TOptions) => void
73
- : TOptions extends object
74
- ? (app: App, options?: TOptions) => void
75
- : (app: App) => void
78
+ export type AppMode = 'sequential' | 'parallel'
79
+
80
+ type AllOptional<T> = {} extends T ? true : false
81
+
82
+ export type Install<TOptions = unknown> = TOptions extends any[]
83
+ ? (app: App, ...options: TOptions) => void | Promise<void>
84
+ : AllOptional<TOptions> extends true
85
+ ? (app: App, options: TOptions | undefined) => void | Promise<void>
86
+ : (app: App, options: TOptions) => void | Promise<void>
76
87
 
77
- export type Inject<TOptions = any[] | object | undefined, TAppExtension extends Record<string, any> = {}> = TOptions extends any[]
88
+ export type Inject<TOptions = unknown, TAppExtension extends Record<string, any> = {}> = TOptions extends any[]
78
89
  ? (app: App, ...options: TOptions) => Partial<TAppExtension>
79
- : TOptions extends object
80
- ? (app: App, options?: TOptions) => Partial<TAppExtension>
81
- : (app: App) => Partial<TAppExtension>
90
+ : AllOptional<TOptions> extends true
91
+ ? (app: App, options: TOptions | undefined) => Partial<TAppExtension>
92
+ : (app: App, options: TOptions) => Partial<TAppExtension>
82
93
 
83
- export interface App<TOptions = unknown> extends Kubb.App {
94
+ export interface App<TOptions extends AppOptions = AppOptions> extends Kubb.App {
84
95
  context: AppContext<TOptions>
85
96
  files: Array<KubbFile.ResolvedFile>
86
- // sync
87
- use<TOptions extends any[] | object = any, TMeta extends object = object, TAppExtension extends Record<string, any> = {}>(
88
- pluginOrParser: Plugin<TOptions, TAppExtension> | Parser<TOptions, TMeta>,
89
- ...options: TOptions extends any[] ? NoInfer<TOptions> : [NoInfer<TOptions>]
90
- ): this & TAppExtension
91
- use<TOptions extends any[] | object = any, TMeta extends object = object, TAppExtension extends Record<string, any> = {}>(
92
- pluginOrParser: Plugin<TOptions, TAppExtension> | Parser<TOptions, TMeta>,
93
- ): this & TAppExtension
94
- // async
95
- use<TOptions extends any[] | object = any, TMeta extends object = object, TAppExtension extends Record<string, any> = {}>(
96
- pluginOrParser: Plugin<TOptions, TAppExtension> | Parser<TOptions, TMeta>,
97
- ...options: TOptions extends any[] ? NoInfer<TOptions> : [NoInfer<TOptions>]
98
- ): Promise<this & TAppExtension>
99
- use<TOptions extends any[] | object = any, TMeta extends object = object, TAppExtension extends Record<string, any> = {}>(
100
- pluginOrParser: Plugin<TOptions, TAppExtension> | Parser<TOptions, TMeta>,
101
- ): Promise<this & TAppExtension>
97
+ use<TPluginOptions = unknown, TMeta extends object = object, TAppExtension extends Record<string, any> = {}>(
98
+ pluginOrParser: Plugin<TPluginOptions, TAppExtension> | Parser<TPluginOptions, TMeta>,
99
+ ...options: TPluginOptions extends any[]
100
+ ? NoInfer<TPluginOptions>
101
+ : AllOptional<TPluginOptions> extends true
102
+ ? [NoInfer<TPluginOptions>?] // Optional when all props are optional
103
+ : [NoInfer<TPluginOptions>] // Required otherwise
104
+ ): (this & TAppExtension) | Promise<this & TAppExtension>
102
105
  addFile(...files: Array<KubbFile.File>): Promise<void>
103
106
  }
@@ -22,10 +22,13 @@ type Options = {
22
22
 
23
23
  export class FileManager {
24
24
  #cache = new Cache<KubbFile.ResolvedFile>()
25
+ events: AsyncEventEmitter<AppEvents>
25
26
  processor: FileProcessor
26
27
 
27
28
  constructor({ events = new AsyncEventEmitter<AppEvents>() }: Options = {}) {
28
29
  this.processor = new FileProcessor({ events })
30
+
31
+ this.events = events
29
32
  return this
30
33
  }
31
34
 
@@ -86,6 +89,10 @@ export class FileManager {
86
89
  }
87
90
 
88
91
  async write(options: ProcessFilesProps): Promise<KubbFile.ResolvedFile[]> {
89
- return this.processor.run(this.files, options)
92
+ const resolvedFiles = await this.processor.run(this.files, options)
93
+
94
+ this.clear()
95
+
96
+ return resolvedFiles
90
97
  }
91
98
  }
@@ -4,12 +4,16 @@ import pLimit from 'p-limit'
4
4
  import type { Parser } from './parsers/types.ts'
5
5
  import { defaultParser } from './parsers/defaultParser.ts'
6
6
  import { AsyncEventEmitter } from './utils/AsyncEventEmitter.ts'
7
- import type { AppEvents } from './App.ts'
7
+ import type { AppEvents, AppMode } from './App.ts'
8
8
 
9
9
  export type ProcessFilesProps = {
10
10
  parsers?: Set<Parser>
11
11
  extension?: Record<KubbFile.Extname, KubbFile.Extname | ''>
12
12
  dryRun?: boolean
13
+ /**
14
+ * @default 'sequential'
15
+ */
16
+ mode?: AppMode
13
17
  }
14
18
 
15
19
  type GetParseOptions = {
@@ -31,9 +35,15 @@ export class FileProcessor {
31
35
  return this
32
36
  }
33
37
 
34
- async parse(file: KubbFile.ResolvedFile, { parsers = new Set(), extension }: GetParseOptions = {}): Promise<string> {
38
+ async parse(file: KubbFile.ResolvedFile, { parsers, extension }: GetParseOptions = {}): Promise<string> {
35
39
  const parseExtName = extension?.[file.extname] || undefined
36
40
 
41
+ if (!parsers) {
42
+ console.warn('No parsers provided, using default parser. If you want to use a specific parser, please provide it in the options.')
43
+
44
+ return defaultParser.parse(file, { extname: parseExtName })
45
+ }
46
+
37
47
  if (!file.extname) {
38
48
  return defaultParser.parse(file, { extname: parseExtName })
39
49
  }
@@ -53,31 +63,51 @@ export class FileProcessor {
53
63
  return parser.parse(file, { extname: parseExtName })
54
64
  }
55
65
 
56
- async run(files: Array<KubbFile.ResolvedFile>, { parsers, dryRun, extension }: ProcessFilesProps = {}): Promise<KubbFile.ResolvedFile[]> {
66
+ async run(
67
+ files: Array<KubbFile.ResolvedFile>,
68
+ { parsers, mode = 'sequential', dryRun, extension }: ProcessFilesProps = {},
69
+ ): Promise<KubbFile.ResolvedFile[]> {
57
70
  await this.events.emit('process:start', { files })
58
71
 
59
72
  let processed = 0
60
73
  const total = files.length
61
74
 
62
- const promises = files.map((resolvedFile, index) =>
63
- this.#limit(async () => {
64
- await this.events.emit('file:start', { file: resolvedFile, index, total })
75
+ const processOne = async (resolvedFile: KubbFile.ResolvedFile, index: number) => {
76
+ const percentage = (processed / total) * 100
65
77
 
66
- if (!dryRun) {
67
- const source = await this.parse(resolvedFile, { extension, parsers })
68
- const nextProcessed = processed + 1
69
- const percentage = (nextProcessed / total) * 100
70
- processed = nextProcessed
71
- await this.events.emit('process:progress', { file: resolvedFile, source, processed, percentage, total })
72
- }
78
+ await this.events.emit('file:start', { file: resolvedFile, index, total })
79
+
80
+ const source = dryRun ? undefined : await this.parse(resolvedFile, { extension, parsers })
81
+
82
+ await this.events.emit('process:progress', {
83
+ file: resolvedFile,
84
+ source,
85
+ processed,
86
+ percentage,
87
+ total,
88
+ })
73
89
 
74
- await this.events.emit('file:end', { file: resolvedFile, index, total })
90
+ processed++
75
91
 
76
- processed++
77
- }),
78
- )
92
+ await this.events.emit('file:end', { file: resolvedFile, index, total })
93
+ }
94
+
95
+ if (mode === 'sequential') {
96
+ async function* asyncFiles() {
97
+ for (let index = 0; index < files.length; index++) {
98
+ yield [files[index], index] as const
99
+ }
100
+ }
79
101
 
80
- await Promise.all(promises)
102
+ for await (const [file, index] of asyncFiles()) {
103
+ if (file) {
104
+ await processOne(file, index)
105
+ }
106
+ }
107
+ } else {
108
+ const promises = files.map((resolvedFile, index) => this.#limit(() => processOne(resolvedFile, index)))
109
+ await Promise.all(promises)
110
+ }
81
111
 
82
112
  await this.events.emit('process:end', { files })
83
113
 
package/src/defineApp.ts CHANGED
@@ -3,19 +3,19 @@ import { isFunction } from 'remeda'
3
3
  import type { Plugin } from './plugins/types.ts'
4
4
  import type { Parser } from './parsers/types.ts'
5
5
  import { AsyncEventEmitter } from './utils/AsyncEventEmitter.ts'
6
- import type { AppContext, Component, AppEvents } from './App.ts'
6
+ import type { AppContext, AppEvents, AppOptions } from './App.ts'
7
7
 
8
8
  import type { App } from './index.ts'
9
9
 
10
10
  type RootRenderFunction<TApp extends App> = (app: TApp) => void | Promise<void>
11
11
 
12
- export type DefineApp<TOptions> = (rootComponent?: Component, options?: TOptions) => App
12
+ export type DefineApp<TOptions> = (options?: TOptions) => App
13
13
 
14
- export function defineApp<TOptions = unknown>(instance?: RootRenderFunction<App<TOptions>>): DefineApp<TOptions> {
14
+ export function defineApp<TOptions extends AppOptions>(instance?: RootRenderFunction<App<TOptions>>): DefineApp<TOptions> {
15
15
  function createApp(options?: TOptions): App {
16
16
  const events = new AsyncEventEmitter<AppEvents>()
17
- const installedPlugins = new Set<Plugin>()
18
- const installedParsers = new Set<Parser>()
17
+ const installedPlugins = new Set<Plugin<any>>()
18
+ const installedParsers = new Set<Parser<any>>()
19
19
  const fileManager = new FileManager({ events })
20
20
  const context = {
21
21
  events,
@@ -68,15 +68,10 @@ export function defineApp<TOptions = unknown>(instance?: RootRenderFunction<App<
68
68
  },
69
69
  } as App<TOptions>
70
70
 
71
- // start
72
- events.emit('start')
73
71
  if (instance) {
74
72
  instance(app)
75
73
  }
76
74
 
77
- // end
78
- events.emit('end')
79
-
80
75
  return app
81
76
  }
82
77
 
@@ -1,6 +1,6 @@
1
1
  import type { Parser, UserParser } from './types.ts'
2
2
 
3
- export function createParser<TOptions = any[], TMeta extends object = any>(parser: UserParser<TOptions, TMeta>): Parser<TOptions, TMeta> {
3
+ export function createParser<TOptions = unknown, TMeta extends object = any>(parser: UserParser<TOptions, TMeta>): Parser<TOptions, TMeta> {
4
4
  return {
5
5
  type: 'parser',
6
6
  ...parser,
@@ -5,7 +5,7 @@ type PrintOptions = {
5
5
  extname?: KubbFile.Extname
6
6
  }
7
7
 
8
- export type Parser<TOptions = any[], TMeta extends object = any> = {
8
+ export type Parser<TOptions = unknown, TMeta extends object = any> = {
9
9
  name: string
10
10
  type: 'parser'
11
11
  /**
@@ -19,4 +19,4 @@ export type Parser<TOptions = any[], TMeta extends object = any> = {
19
19
  parse(file: KubbFile.ResolvedFile<TMeta>, options: PrintOptions): Promise<string>
20
20
  }
21
21
 
22
- export type UserParser<TOptions = any[], TMeta extends object = any> = Omit<Parser<TOptions, TMeta>, 'type'>
22
+ export type UserParser<TOptions = unknown, TMeta extends object = any> = Omit<Parser<TOptions, TMeta>, 'type'>
@@ -0,0 +1,207 @@
1
+ /** biome-ignore-all lint/suspicious/useIterableCallbackReturn: not needed */
2
+
3
+ import { createPlugin } from './createPlugin.ts'
4
+ import type * as KubbFile from '../KubbFile.ts'
5
+ import { TreeNode } from '../utils/TreeNode.ts'
6
+ import path, { resolve } from 'node:path'
7
+ import { getRelativePath } from '../utils/getRelativePath.ts'
8
+ import { createFile } from '../createFile.ts'
9
+
10
+ type Mode = 'all' | 'named' | 'propagate' | false
11
+
12
+ type Options = {
13
+ root: string
14
+ mode: Mode
15
+ dryRun?: boolean
16
+ }
17
+
18
+ type WriteEntryOptions = {
19
+ root: string
20
+ mode: Mode
21
+ }
22
+
23
+ type ExtendOptions = {
24
+ writeEntry(options: WriteEntryOptions): Promise<void>
25
+ }
26
+
27
+ // biome-ignore lint/suspicious/noTsIgnore: production ready
28
+ // @ts-ignore
29
+ declare module '@kubb/fabric-core' {
30
+ interface App {
31
+ writeEntry(options: WriteEntryOptions): Promise<void>
32
+ }
33
+ }
34
+
35
+ declare global {
36
+ namespace Kubb {
37
+ interface App {
38
+ writeEntry(options: WriteEntryOptions): Promise<void>
39
+ }
40
+ }
41
+ }
42
+
43
+ type GetBarrelFilesOptions = {
44
+ files: KubbFile.File[]
45
+ root: string
46
+ mode: Mode
47
+ }
48
+
49
+ export function getBarrelFiles({ files, root, mode }: GetBarrelFilesOptions): Array<KubbFile.File> {
50
+ // Do not generate when propagating or disabled
51
+ if (mode === 'propagate' || mode === false) {
52
+ return []
53
+ }
54
+
55
+ const cachedFiles = new Map<KubbFile.Path, KubbFile.File>()
56
+ const dedupe = new Map<KubbFile.Path, Set<string>>()
57
+
58
+ const tree = TreeNode.fromFiles(files, root)
59
+ tree?.forEach((node) => {
60
+ // Only create a barrel for directory-like nodes that have a parent with a path
61
+ if (!node?.children || !node.parent?.data.path) {
62
+ return
63
+ }
64
+
65
+ const parentPath = node.parent.data.path as KubbFile.Path
66
+ const barrelPath = path.join(parentPath, 'index.ts') as KubbFile.Path
67
+
68
+ let barrelFile = cachedFiles.get(barrelPath)
69
+ if (!barrelFile) {
70
+ barrelFile = createFile({
71
+ path: barrelPath,
72
+ baseName: 'index.ts',
73
+ exports: [],
74
+ sources: [],
75
+ })
76
+ cachedFiles.set(barrelPath, barrelFile)
77
+ dedupe.set(barrelPath, new Set<string>())
78
+ }
79
+
80
+ const seen = dedupe.get(barrelPath)!
81
+
82
+ // Collect all leaves under the current directory node
83
+ node.leaves.forEach((leaf) => {
84
+ const file = leaf.data.file
85
+ if (!file) {
86
+ return
87
+ }
88
+
89
+ const sources = file.sources || []
90
+ sources.forEach((source) => {
91
+ if (!file.path || !source.isIndexable || !source.name) {
92
+ return
93
+ }
94
+
95
+ const key = `${source.name}|${source.isTypeOnly ? '1' : '0'}`
96
+ if (seen.has(key)) {
97
+ return
98
+ }
99
+ seen.add(key)
100
+
101
+ // Always compute relative path from the parent directory to the file path
102
+ barrelFile!.exports!.push({
103
+ name: [source.name],
104
+ path: getRelativePath(parentPath, file.path),
105
+ isTypeOnly: source.isTypeOnly,
106
+ })
107
+
108
+ barrelFile!.sources.push({
109
+ name: source.name,
110
+ isTypeOnly: source.isTypeOnly,
111
+ value: '', // TODO use parser to generate import
112
+ isExportable: mode === 'all' || mode === 'named',
113
+ isIndexable: mode === 'all' || mode === 'named',
114
+ })
115
+ })
116
+ })
117
+ })
118
+
119
+ const result = [...cachedFiles.values()]
120
+
121
+ if (mode === 'all') {
122
+ return result.map((file) => ({
123
+ ...file,
124
+ exports: file.exports?.map((e) => ({ ...e, name: undefined })),
125
+ }))
126
+ }
127
+
128
+ return result
129
+ }
130
+
131
+ export const barrelPlugin = createPlugin<Options, ExtendOptions>({
132
+ name: 'barrel',
133
+ install(app, options) {
134
+ if (!options) {
135
+ throw new Error('Barrel plugin requires options.root and options.mode')
136
+ }
137
+
138
+ if (!options.mode) {
139
+ return undefined
140
+ }
141
+
142
+ app.context.events.onOnce('process:end', async ({ files }) => {
143
+ const root = options.root
144
+ const barrelFiles = getBarrelFiles({ files, root, mode: options.mode })
145
+
146
+ await app.context.fileManager.add(...barrelFiles)
147
+
148
+ await app.context.fileManager.write({
149
+ mode: app.context.options?.mode,
150
+ dryRun: options.dryRun,
151
+ parsers: app.context.installedParsers,
152
+ })
153
+ })
154
+ },
155
+ inject(app, options) {
156
+ if (!options) {
157
+ throw new Error('Barrel plugin requires options.root and options.mode')
158
+ }
159
+
160
+ return {
161
+ async writeEntry({ root, mode }) {
162
+ if (!mode || mode === 'propagate') {
163
+ return undefined
164
+ }
165
+
166
+ const rootPath = resolve(root, 'index.ts')
167
+
168
+ const barrelFiles = app.files.filter((file) => {
169
+ return file.sources.some((source) => source.isIndexable)
170
+ })
171
+
172
+ const entryFile = createFile({
173
+ path: rootPath,
174
+ baseName: 'index.ts',
175
+ exports: barrelFiles
176
+ .flatMap((file) => {
177
+ const containsOnlyTypes = file.sources.every((source) => source.isTypeOnly)
178
+
179
+ return file.sources
180
+ ?.map((source) => {
181
+ if (!file.path || !source.isIndexable) {
182
+ return undefined
183
+ }
184
+
185
+ return {
186
+ name: mode === 'all' ? undefined : [source.name],
187
+ path: getRelativePath(rootPath, file.path),
188
+ isTypeOnly: mode === 'all' ? containsOnlyTypes : source.isTypeOnly,
189
+ } as KubbFile.Export
190
+ })
191
+ .filter(Boolean)
192
+ })
193
+ .filter(Boolean),
194
+ sources: [],
195
+ })
196
+
197
+ await app.context.fileManager.add(entryFile)
198
+
199
+ await app.context.fileManager.write({
200
+ mode: app.context.options?.mode,
201
+ dryRun: options.dryRun,
202
+ parsers: app.context.installedParsers,
203
+ })
204
+ },
205
+ }
206
+ },
207
+ })
@@ -1,6 +1,6 @@
1
1
  import type { Plugin, UserPlugin } from './types.ts'
2
2
 
3
- export function createPlugin<Options = any[], TAppExtension extends Record<string, any> = {}>(
3
+ export function createPlugin<Options = unknown, TAppExtension extends Record<string, any> = {}>(
4
4
  plugin: UserPlugin<Options, TAppExtension>,
5
5
  ): Plugin<Options, TAppExtension> {
6
6
  return {
@@ -6,15 +6,15 @@ import type * as KubbFile from '../KubbFile.ts'
6
6
 
7
7
  type WriteOptions = {
8
8
  extension?: Record<KubbFile.Extname, KubbFile.Extname | ''>
9
- dryRun?: boolean
10
9
  }
11
10
 
12
11
  type Options = {
12
+ dryRun?: boolean
13
13
  /**
14
14
  * Optional callback that is invoked whenever a file is written by the plugin.
15
15
  * Useful for tests to observe write operations without spying on internal functions.
16
16
  */
17
- onWrite?: (path: string, data: string) => void | Promise<void>
17
+ onBeforeWrite?: (path: string, data: string | undefined) => void | Promise<void>
18
18
  clean?: {
19
19
  path: string
20
20
  }
@@ -24,13 +24,14 @@ type ExtendOptions = {
24
24
  write(options?: WriteOptions): Promise<void>
25
25
  }
26
26
 
27
- export async function write(path: string, data: string, options: { sanity?: boolean } = {}): Promise<string | undefined> {
28
- if (data.trim() === '') {
29
- return undefined
30
- }
27
+ export async function write(path: string, data: string | undefined, options: { sanity?: boolean } = {}): Promise<string | undefined> {
31
28
  return switcher(
32
29
  {
33
- node: async (path: string, data: string, { sanity }: { sanity?: boolean }) => {
30
+ node: async (path, data: string | undefined, { sanity }: { sanity?: boolean }) => {
31
+ if (!data || data?.trim() === '') {
32
+ return undefined
33
+ }
34
+
34
35
  try {
35
36
  const oldContent = await fs.readFile(resolve(path), {
36
37
  encoding: 'utf-8',
@@ -42,7 +43,7 @@ export async function write(path: string, data: string, options: { sanity?: bool
42
43
  /* empty */
43
44
  }
44
45
 
45
- await fs.outputFile(resolve(path), data, { encoding: 'utf-8' })
46
+ await fs.outputFile(resolve(path), data.trim(), { encoding: 'utf-8' })
46
47
 
47
48
  if (sanity) {
48
49
  const savedData = await fs.readFile(resolve(path), {
@@ -58,29 +59,29 @@ export async function write(path: string, data: string, options: { sanity?: bool
58
59
 
59
60
  return data
60
61
  },
61
- bun: async (path: string, data: string, { sanity }: { sanity?: boolean }) => {
62
- try {
63
- await Bun.write(resolve(path), data)
62
+ bun: async (path: string, data: string | undefined, { sanity }: { sanity?: boolean }) => {
63
+ if (!data || data?.trim() === '') {
64
+ return undefined
65
+ }
64
66
 
65
- if (sanity) {
66
- const file = Bun.file(resolve(path))
67
- const savedData = await file.text()
67
+ await Bun.write(resolve(path), data.trim())
68
68
 
69
- if (savedData?.toString() !== data?.toString()) {
70
- throw new Error(`Sanity check failed for ${path}\n\nData[${path.length}]:\n${path}\n\nSaved[${savedData.length}]:\n${savedData}\n`)
71
- }
69
+ if (sanity) {
70
+ const file = Bun.file(resolve(path))
71
+ const savedData = await file.text()
72
72
 
73
- return savedData
73
+ if (savedData?.toString() !== data?.toString()) {
74
+ throw new Error(`Sanity check failed for ${path}\n\nData[${path.length}]:\n${path}\n\nSaved[${savedData.length}]:\n${savedData}\n`)
74
75
  }
75
76
 
76
- return data
77
- } catch (e) {
78
- console.error(e)
77
+ return savedData
79
78
  }
79
+
80
+ return data
80
81
  },
81
82
  },
82
83
  'node',
83
- )(path, data.trim(), options)
84
+ )(path, data, options)
84
85
  }
85
86
 
86
87
  // biome-ignore lint/suspicious/noTsIgnore: production ready
@@ -101,31 +102,29 @@ declare global {
101
102
 
102
103
  export const fsPlugin = createPlugin<Options, ExtendOptions>({
103
104
  name: 'fs',
104
- async install(app, options) {
105
+ install(app, options = {}) {
106
+ if (options.clean) {
107
+ fs.removeSync(options.clean.path)
108
+ }
109
+
105
110
  app.context.events.on('process:progress', async ({ file, source }) => {
106
- if (options?.onWrite) {
107
- await options.onWrite(file.path, source)
111
+ if (options.onBeforeWrite) {
112
+ await options.onBeforeWrite(file.path, source)
108
113
  }
109
114
  await write(file.path, source, { sanity: false })
110
115
  })
111
-
112
- app.context.events.on('start', async () => {
113
- if (options?.clean) {
114
- await fs.remove(options.clean.path)
115
- }
116
- })
117
116
  },
118
- inject(app) {
117
+ inject(app, { dryRun } = {}) {
119
118
  return {
120
119
  async write(
121
120
  options = {
122
121
  extension: { '.ts': '.ts' },
123
- dryRun: false,
124
122
  },
125
123
  ) {
126
124
  await app.context.fileManager.write({
125
+ mode: app.context.options?.mode,
127
126
  extension: options.extension,
128
- dryRun: options.dryRun,
127
+ dryRun,
129
128
  parsers: app.context.installedParsers,
130
129
  })
131
130
  },
@@ -1,3 +1,5 @@
1
1
  export { createPlugin } from './createPlugin.ts'
2
2
 
3
3
  export { fsPlugin } from './fsPlugin.ts'
4
+ export { barrelPlugin } from './barrelPlugin.ts'
5
+ export { progressPlugin } from './progressPlugin.ts'