@kubb/core 5.0.0-alpha.2 → 5.0.0-alpha.20

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 (55) hide show
  1. package/dist/{types-B7eZvqwD.d.ts → PluginDriver-BkSenc-R.d.ts} +521 -299
  2. package/dist/hooks.cjs +101 -8
  3. package/dist/hooks.cjs.map +1 -1
  4. package/dist/hooks.d.ts +83 -4
  5. package/dist/hooks.js +99 -8
  6. package/dist/hooks.js.map +1 -1
  7. package/dist/index.cjs +850 -536
  8. package/dist/index.cjs.map +1 -1
  9. package/dist/index.d.ts +438 -89
  10. package/dist/index.js +839 -532
  11. package/dist/index.js.map +1 -1
  12. package/package.json +6 -6
  13. package/src/Kubb.ts +37 -55
  14. package/src/{PluginManager.ts → PluginDriver.ts} +51 -40
  15. package/src/build.ts +74 -29
  16. package/src/config.ts +9 -8
  17. package/src/constants.ts +44 -1
  18. package/src/createAdapter.ts +25 -0
  19. package/src/createPlugin.ts +28 -0
  20. package/src/createStorage.ts +58 -0
  21. package/src/defineGenerator.ts +134 -0
  22. package/src/defineLogger.ts +13 -3
  23. package/src/definePreset.ts +23 -0
  24. package/src/definePresets.ts +16 -0
  25. package/src/defineResolver.ts +131 -0
  26. package/src/hooks/index.ts +2 -1
  27. package/src/hooks/useKubb.ts +160 -0
  28. package/src/hooks/useMode.ts +5 -2
  29. package/src/hooks/usePlugin.ts +5 -2
  30. package/src/hooks/usePluginDriver.ts +11 -0
  31. package/src/index.ts +12 -6
  32. package/src/renderNode.tsx +108 -0
  33. package/src/storages/fsStorage.ts +2 -2
  34. package/src/storages/memoryStorage.ts +2 -2
  35. package/src/types.ts +150 -38
  36. package/src/utils/FunctionParams.ts +2 -2
  37. package/src/utils/TreeNode.ts +24 -1
  38. package/src/utils/diagnostics.ts +4 -1
  39. package/src/utils/executeStrategies.ts +23 -10
  40. package/src/utils/formatters.ts +10 -21
  41. package/src/utils/getBarrelFiles.ts +79 -9
  42. package/src/utils/getConfigs.ts +8 -22
  43. package/src/utils/getPreset.ts +41 -0
  44. package/src/utils/linters.ts +23 -3
  45. package/src/utils/mergeResolvers.ts +8 -0
  46. package/src/utils/packageJSON.ts +76 -0
  47. package/src/BarrelManager.ts +0 -74
  48. package/src/PackageManager.ts +0 -180
  49. package/src/PromiseManager.ts +0 -40
  50. package/src/defineAdapter.ts +0 -22
  51. package/src/definePlugin.ts +0 -12
  52. package/src/defineStorage.ts +0 -56
  53. package/src/errors.ts +0 -1
  54. package/src/hooks/usePluginManager.ts +0 -8
  55. package/src/utils/getPlugins.ts +0 -23
package/src/types.ts CHANGED
@@ -1,11 +1,10 @@
1
1
  import type { AsyncEventEmitter, PossiblePromise } from '@internals/utils'
2
- import type { RootNode } from '@kubb/ast/types'
3
- import type { KubbFile } from '@kubb/fabric-core/types'
4
- import type { Fabric } from '@kubb/react-fabric'
2
+ import type { Node, RootNode, SchemaNode, Visitor } from '@kubb/ast/types'
3
+ import type { Fabric as FabricType, KubbFile } from '@kubb/fabric-core/types'
5
4
  import type { DEFAULT_STUDIO_URL, logLevel } from './constants.ts'
6
- import type { DefineStorage } from './defineStorage.ts'
5
+ import type { Storage } from './createStorage.ts'
7
6
  import type { KubbEvents } from './Kubb.ts'
8
- import type { PluginManager } from './PluginManager.ts'
7
+ import type { PluginDriver } from './PluginDriver.ts'
9
8
 
10
9
  export type { Printer, PrinterFactoryOptions } from '@kubb/ast/types'
11
10
 
@@ -33,7 +32,7 @@ export type UserConfig<TInput = Input> = Omit<Config<TInput>, 'root' | 'plugins'
33
32
  /**
34
33
  * An array of Kubb plugins used for generation. Each plugin may have additional configurable options (defined within the plugin itself). If a plugin relies on another plugin, an error will occur if the required dependency is missing. Refer to “pre” for more details.
35
34
  */
36
- // inject needs to be omitted because else we have a clash with the PluginManager instance
35
+ // inject needs to be omitted because else we have a clash with the PluginDriver instance
37
36
  plugins?: Array<Omit<UnknownUserPlugin, 'inject'>>
38
37
  }
39
38
 
@@ -66,11 +65,18 @@ export type AdapterSource = { type: 'path'; path: string } | { type: 'data'; dat
66
65
  * - `TName` — unique string identifier (e.g. `'oas'`, `'asyncapi'`)
67
66
  * - `TOptions` — raw user-facing options passed to the adapter factory
68
67
  * - `TResolvedOptions` — defaults applied; what the adapter stores as `options`
68
+ * - `TDocument` — type of the raw source document exposed by the adapter after `parse()`
69
69
  */
70
- export type AdapterFactoryOptions<TName extends string = string, TOptions extends object = object, TResolvedOptions extends object = TOptions> = {
70
+ export type AdapterFactoryOptions<
71
+ TName extends string = string,
72
+ TOptions extends object = object,
73
+ TResolvedOptions extends object = TOptions,
74
+ TDocument = unknown,
75
+ > = {
71
76
  name: TName
72
77
  options: TOptions
73
78
  resolvedOptions: TResolvedOptions
79
+ document: TDocument
74
80
  }
75
81
 
76
82
  /**
@@ -92,12 +98,31 @@ export type AdapterFactoryOptions<TName extends string = string, TOptions extend
92
98
  * ```
93
99
  */
94
100
  export type Adapter<TOptions extends AdapterFactoryOptions = AdapterFactoryOptions> = {
95
- /** Human-readable identifier, e.g. `'oas'`, `'drizzle'`, `'asyncapi'`. */
101
+ /**
102
+ * Human-readable identifier, e.g. `'oas'`, `'drizzle'`, `'asyncapi'`.
103
+ */
96
104
  name: TOptions['name']
97
- /** Resolved options (after defaults have been applied). */
105
+ /**
106
+ * Resolved options (after defaults have been applied).
107
+ */
98
108
  options: TOptions['resolvedOptions']
99
- /** Convert the raw source into a universal `RootNode`. */
109
+ /**
110
+ * The raw source document produced after the first `parse()` call.
111
+ * `undefined` before parsing; typed by the adapter's `TDocument` generic.
112
+ */
113
+ document?: TOptions['document']
114
+ /**
115
+ * Convert the raw source into a universal `RootNode`.
116
+ */
100
117
  parse: (source: AdapterSource) => PossiblePromise<RootNode>
118
+ /**
119
+ * Extracts `KubbFile.Import` entries needed by a `SchemaNode` tree.
120
+ * Populated after the first `parse()` call. Returns an empty array before that.
121
+ *
122
+ * The `resolve` callback receives the collision-corrected schema name and must
123
+ * return the `{ name, path }` pair for the import, or `undefined` to skip it.
124
+ */
125
+ getImports: (node: SchemaNode, resolve: (schemaName: string) => { name: string; path: string }) => Array<KubbFile.Import>
101
126
  }
102
127
 
103
128
  export type BarrelType = 'all' | 'named' | 'propagate'
@@ -165,16 +190,16 @@ export type Config<TInput = Input> = {
165
190
  /**
166
191
  * Storage backend for generated files.
167
192
  * Defaults to `fsStorage()` — the built-in filesystem driver.
168
- * Accepts any object implementing the {@link DefineStorage} interface.
193
+ * Accepts any object implementing the {@link Storage} interface.
169
194
  * Keys are root-relative paths (e.g. `src/gen/api/getPets.ts`).
170
195
  * @default fsStorage()
171
196
  * @example
172
197
  * ```ts
173
- * import { defineStorage, fsStorage } from '@kubb/core'
174
- * storage: defineStorage(fsStorage())
198
+ * import { memoryStorage } from '@kubb/core'
199
+ * storage: memoryStorage()
175
200
  * ```
176
201
  */
177
- storage?: DefineStorage
202
+ storage?: Storage
178
203
  /**
179
204
  * Specifies the formatting tool to be used.
180
205
  * - 'auto' automatically detects and uses biome or prettier (in that order of preference).
@@ -253,6 +278,45 @@ export type Config<TInput = Input> = {
253
278
 
254
279
  // plugin
255
280
 
281
+ type PatternFilter = {
282
+ type: string
283
+ pattern: string | RegExp
284
+ }
285
+
286
+ type PatternOverride<TOptions> = PatternFilter & {
287
+ options: Omit<Partial<TOptions>, 'override'>
288
+ }
289
+
290
+ export type ResolveOptionsContext<TOptions> = {
291
+ options: TOptions
292
+ exclude?: Array<PatternFilter>
293
+ include?: Array<PatternFilter>
294
+ override?: Array<PatternOverride<TOptions>>
295
+ }
296
+
297
+ /**
298
+ * Base constraint for all plugin resolver objects.
299
+ *
300
+ * `default` and `resolveOptions` are injected automatically by `defineResolver` — plugin
301
+ * authors may override them but never need to implement them from scratch.
302
+ * Concrete plugin resolver types extend this with their own helper methods.
303
+ */
304
+ export type Resolver = {
305
+ name: string
306
+ default(name: ResolveNameParams['name'], type?: ResolveNameParams['type']): string
307
+ resolveOptions<TOptions>(node: Node, context: ResolveOptionsContext<TOptions>): TOptions | null
308
+ }
309
+
310
+ /**
311
+ * The user-facing subset of a `Resolver` — everything except the methods injected by
312
+ * `defineResolver` (`default` and `resolveOptions`).
313
+ *
314
+ * When you pass a `UserResolver` to `defineResolver`, the standard `default` and
315
+ * `resolveOptions` implementations are injected automatically so plugin authors never
316
+ * need to define them by hand. Both can still be overridden by providing them explicitly.
317
+ */
318
+ export type UserResolver = Omit<Resolver, 'default' | 'resolveOptions'>
319
+
256
320
  export type PluginFactoryOptions<
257
321
  /**
258
322
  * Name to be used for the plugin.
@@ -274,16 +338,20 @@ export type PluginFactoryOptions<
274
338
  * When calling `resolvePath` you can specify better types.
275
339
  */
276
340
  TResolvePathOptions extends object = object,
341
+ /**
342
+ * Resolver object that encapsulates the naming and path-resolution helpers used by this plugin.
343
+ * Use `defineResolver` to define the resolver object and export it alongside the plugin.
344
+ */
345
+ TResolver extends Resolver = Resolver,
277
346
  > = {
278
347
  name: TName
279
348
  options: TOptions
280
349
  resolvedOptions: TResolvedOptions
281
350
  context: TContext
282
351
  resolvePathOptions: TResolvePathOptions
352
+ resolver: TResolver
283
353
  }
284
354
 
285
- export type GetPluginFactoryOptions<TPlugin extends UserPlugin> = TPlugin extends UserPlugin<infer X> ? X : never
286
-
287
355
  export type UserPlugin<TOptions extends PluginFactoryOptions = PluginFactoryOptions> = {
288
356
  /**
289
357
  * Unique name used for the plugin
@@ -309,7 +377,7 @@ export type UserPlugin<TOptions extends PluginFactoryOptions = PluginFactoryOpti
309
377
 
310
378
  export type UserPluginWithLifeCycle<TOptions extends PluginFactoryOptions = PluginFactoryOptions> = UserPlugin<TOptions> & PluginLifecycle<TOptions>
311
379
 
312
- export type UnknownUserPlugin = UserPlugin<PluginFactoryOptions<any, any, any, any, any>>
380
+ type UnknownUserPlugin = UserPlugin<PluginFactoryOptions<string, object, object, unknown, object>>
313
381
 
314
382
  export type Plugin<TOptions extends PluginFactoryOptions = PluginFactoryOptions> = {
315
383
  /**
@@ -333,7 +401,7 @@ export type Plugin<TOptions extends PluginFactoryOptions = PluginFactoryOptions>
333
401
 
334
402
  install: (this: PluginContext<TOptions>, context: PluginContext<TOptions>) => PossiblePromise<void>
335
403
  /**
336
- * Define a context that can be used by other plugins, see `PluginManager' where we convert from `UserPlugin` to `Plugin`(used when calling `definePlugin`).
404
+ * Defines a context that can be used by other plugins, see `PluginDriver` where we convert from `UserPlugin` to `Plugin` (used when calling `createPlugin`).
337
405
  */
338
406
  inject: (this: PluginContext<TOptions>, context: PluginContext<TOptions>) => TOptions['context']
339
407
  }
@@ -391,9 +459,9 @@ export type ResolveNameParams = {
391
459
  }
392
460
 
393
461
  export type PluginContext<TOptions extends PluginFactoryOptions = PluginFactoryOptions> = {
394
- fabric: Fabric
462
+ fabric: FabricType
395
463
  config: Config
396
- pluginManager: PluginManager
464
+ driver: PluginDriver
397
465
  /**
398
466
  * Only add when the file does not exist yet
399
467
  */
@@ -408,18 +476,31 @@ export type PluginContext<TOptions extends PluginFactoryOptions = PluginFactoryO
408
476
  * Current plugin
409
477
  */
410
478
  plugin: Plugin<TOptions>
411
- /**
412
- * Returns the universal `@kubb/ast` `RootNode` produced by the configured adapter.
413
- * Returns `undefined` when no adapter was set (legacy OAS-only usage).
414
- */
415
- rootNode: RootNode | undefined
479
+
416
480
  /**
417
481
  * Opens the Kubb Studio URL for the current `rootNode` in the default browser.
418
482
  * Falls back to printing the URL if the browser cannot be launched.
419
483
  * No-ops silently when no adapter has set a `rootNode`.
420
484
  */
421
485
  openInStudio: (options?: DevtoolsOptions) => Promise<void>
422
- } & Kubb.PluginContext
486
+ } & (
487
+ | {
488
+ /**
489
+ * Returns the universal `@kubb/ast` `RootNode` produced by the configured adapter.
490
+ * Returns `undefined` when no adapter was set (legacy OAS-only usage).
491
+ */
492
+ rootNode: RootNode
493
+ /**
494
+ * Return the adapter from `@kubb/ast`
495
+ */
496
+ adapter: Adapter
497
+ }
498
+ | {
499
+ rootNode?: never
500
+ adapter?: never
501
+ }
502
+ ) &
503
+ Kubb.PluginContext
423
504
  /**
424
505
  * Specify the export location for the files and define the behavior of the output
425
506
  */
@@ -448,10 +529,6 @@ export type Output<TOptions> = {
448
529
  override?: boolean
449
530
  }
450
531
 
451
- type GroupContext = {
452
- group: string
453
- }
454
-
455
532
  export type Group = {
456
533
  /**
457
534
  * Defines the type where to group the files.
@@ -461,9 +538,9 @@ export type Group = {
461
538
  */
462
539
  type: 'tag' | 'path'
463
540
  /**
464
- * Return the name of a group based on the group name, this used for the file and name generation
541
+ * Return the name of a group based on the group name, this is used for the file and name generation.
465
542
  */
466
- name?: (context: GroupContext) => string
543
+ name?: (context: { group: string }) => string
467
544
  }
468
545
 
469
546
  export type LoggerOptions = {
@@ -476,16 +553,51 @@ export type LoggerOptions = {
476
553
  /**
477
554
  * Shared context passed to all plugins, parsers, and Fabric internals.
478
555
  */
479
- export interface LoggerContext extends AsyncEventEmitter<KubbEvents> {}
480
-
481
- type Install<TOptions = unknown> = (context: LoggerContext, options?: TOptions) => void | Promise<void>
556
+ export type LoggerContext = AsyncEventEmitter<KubbEvents>
482
557
 
483
558
  export type Logger<TOptions extends LoggerOptions = LoggerOptions> = {
484
559
  name: string
485
- install: Install<TOptions>
560
+ install: (context: LoggerContext, options?: TOptions) => void | Promise<void>
486
561
  }
487
562
 
488
- export type UserLogger<TOptions extends LoggerOptions = LoggerOptions> = Omit<Logger<TOptions>, 'logLevel'>
563
+ export type UserLogger<TOptions extends LoggerOptions = LoggerOptions> = Logger<TOptions>
489
564
 
490
- export type { DefineStorage } from './defineStorage.ts'
565
+ /**
566
+ * Compatibility preset for code generation tools.
567
+ * - `'default'` – no compatibility adjustments (default behavior).
568
+ * - `'kubbV4'` – align generated names and structures with Kubb v4 output.
569
+ */
570
+ export type CompatibilityPreset = 'default' | 'kubbV4'
571
+
572
+ export type { Storage } from './createStorage.ts'
573
+ export type { CoreGeneratorV2, Generator, ReactGeneratorV2 } from './defineGenerator.ts'
491
574
  export type { KubbEvents } from './Kubb.ts'
575
+
576
+ /**
577
+ * A preset bundles a name, one or more resolvers, and optional AST transformers
578
+ * into a single reusable configuration object.
579
+ *
580
+ * @template TResolver - The concrete resolver type for this preset.
581
+ */
582
+ export type Preset<TResolver extends Resolver = Resolver> = {
583
+ /**
584
+ * Unique identifier for this preset.
585
+ */
586
+ name: string
587
+ /**
588
+ * Ordered list of resolvers applied by this preset (last entry wins on merge).
589
+ */
590
+ resolvers: Array<TResolver>
591
+ /**
592
+ * Optional AST visitors / transformers applied after resolving.
593
+ */
594
+ transformers?: Array<Visitor>
595
+ }
596
+
597
+ /**
598
+ * A named registry of presets, keyed by preset name.
599
+ *
600
+ * @template TResolver - The concrete resolver type shared by all presets in this registry.
601
+ * @template TName - The union of valid preset name keys.
602
+ */
603
+ export type Presets<TResolver extends Resolver = Resolver> = Record<CompatibilityPreset, Preset<TResolver>>
@@ -30,12 +30,12 @@ type FunctionParamsASTWithType = {
30
30
  default?: string
31
31
  }
32
32
  /**
33
- * @deprecated
33
+ * @deprecated use ast package instead
34
34
  */
35
35
  export type FunctionParamsAST = FunctionParamsASTWithoutType | FunctionParamsASTWithType
36
36
 
37
37
  /**
38
- * @deprecated
38
+ * @deprecated use ast package instead
39
39
  */
40
40
  export class FunctionParams {
41
41
  #items: Array<FunctionParamsAST | FunctionParamsAST[]> = []
@@ -1,6 +1,6 @@
1
1
  import path from 'node:path'
2
2
  import type { KubbFile } from '@kubb/fabric-core/types'
3
- import { getMode } from '../PluginManager.ts'
3
+ import { getMode } from '../PluginDriver.ts'
4
4
 
5
5
  type BarrelData = {
6
6
  file?: KubbFile.File
@@ -12,6 +12,15 @@ type BarrelData = {
12
12
  name: string
13
13
  }
14
14
 
15
+ /**
16
+ * Tree structure used to build per-directory barrel (`index.ts`) files from a
17
+ * flat list of generated {@link KubbFile.File} entries.
18
+ *
19
+ * Each node represents either a directory or a file within the output tree.
20
+ * Use {@link TreeNode.build} to construct a root node from a file list, then
21
+ * traverse with {@link TreeNode.forEach}, {@link TreeNode.leaves}, or the
22
+ * `*Deep` helpers.
23
+ */
15
24
  export class TreeNode {
16
25
  data: BarrelData
17
26
  parent?: TreeNode
@@ -32,6 +41,9 @@ export class TreeNode {
32
41
  return child
33
42
  }
34
43
 
44
+ /**
45
+ * Returns the root ancestor of this node, walking up via `parent` links.
46
+ */
35
47
  get root(): TreeNode {
36
48
  if (!this.parent) {
37
49
  return this
@@ -39,6 +51,11 @@ export class TreeNode {
39
51
  return this.parent.root
40
52
  }
41
53
 
54
+ /**
55
+ * Returns all leaf descendants (nodes with no children) of this node.
56
+ *
57
+ * Results are cached after the first traversal.
58
+ */
42
59
  get leaves(): Array<TreeNode> {
43
60
  if (!this.children || this.children.length === 0) {
44
61
  // this is a leaf
@@ -105,6 +122,12 @@ export class TreeNode {
105
122
  return this.leaves.map(callback)
106
123
  }
107
124
 
125
+ /**
126
+ * Builds a {@link TreeNode} tree from a flat list of files.
127
+ *
128
+ * - Filters to files under `root` (when provided) and skips `.json` files.
129
+ * - Returns `null` when no files match.
130
+ */
108
131
  public static build(files: KubbFile.File[], root?: string): TreeNode | null {
109
132
  try {
110
133
  const filteredTree = buildDirectoryTree(files, root)
@@ -2,7 +2,10 @@ import { version as nodeVersion } from 'node:process'
2
2
  import { version as KubbVersion } from '../../package.json'
3
3
 
4
4
  /**
5
- * Get diagnostic information for debugging
5
+ * Returns a snapshot of the current runtime environment.
6
+ *
7
+ * Useful for attaching context to debug logs and error reports so that
8
+ * issues can be reproduced without manual information gathering.
6
9
  */
7
10
  export function getDiagnosticInfo() {
8
11
  return {
@@ -7,7 +7,11 @@ type ValueOfPromiseFuncArray<TInput extends Array<unknown>> = TInput extends Arr
7
7
  type SeqOutput<TInput extends Array<PromiseFunc<TValue, null>>, TValue> = Promise<Array<Awaited<ValueOfPromiseFuncArray<TInput>>>>
8
8
 
9
9
  /**
10
- * Chains promises
10
+ * Runs promise functions in sequence, threading each result into the next call.
11
+ *
12
+ * - Each function receives the accumulated state from the previous call.
13
+ * - Skips functions that return a falsy value (acts as a no-op for that step).
14
+ * - Returns an array of all individual results.
11
15
  */
12
16
  export function hookSeq<TInput extends Array<PromiseFunc<TValue, null>>, TValue, TOutput = SeqOutput<TInput, TValue>>(promises: TInput): TOutput {
13
17
  return promises.filter(Boolean).reduce(
@@ -33,7 +37,10 @@ export function hookSeq<TInput extends Array<PromiseFunc<TValue, null>>, TValue,
33
37
  type HookFirstOutput<TInput extends Array<PromiseFunc<TValue, null>>, TValue = unknown> = ValueOfPromiseFuncArray<TInput>
34
38
 
35
39
  /**
36
- * Chains promises, first non-null result stops and returns
40
+ * Runs promise functions in sequence and returns the first non-null result.
41
+ *
42
+ * - Stops as soon as `nullCheck` passes for a result (default: `!== null`).
43
+ * - Subsequent functions are skipped once a match is found.
37
44
  */
38
45
  export function hookFirst<TInput extends Array<PromiseFunc<TValue, null>>, TValue = unknown, TOutput = HookFirstOutput<TInput, TValue>>(
39
46
  promises: TInput,
@@ -57,7 +64,10 @@ export function hookFirst<TInput extends Array<PromiseFunc<TValue, null>>, TValu
57
64
  type HookParallelOutput<TInput extends Array<PromiseFunc<TValue, null>>, TValue> = Promise<PromiseSettledResult<Awaited<ValueOfPromiseFuncArray<TInput>>>[]>
58
65
 
59
66
  /**
60
- * Runs an array of promise functions with optional concurrency limit.
67
+ * Runs promise functions concurrently and returns all settled results.
68
+ *
69
+ * - Limits simultaneous executions to `concurrency` (default: unlimited).
70
+ * - Uses `Promise.allSettled` so individual failures do not cancel other tasks.
61
71
  */
62
72
  export function hookParallel<TInput extends Array<PromiseFunc<TValue, null>>, TValue = unknown, TOutput = HookParallelOutput<TInput, TValue>>(
63
73
  promises: TInput,
@@ -70,12 +80,15 @@ export function hookParallel<TInput extends Array<PromiseFunc<TValue, null>>, TV
70
80
  return Promise.allSettled(tasks) as TOutput
71
81
  }
72
82
 
83
+ /**
84
+ * Execution strategy used when dispatching plugin hook calls.
85
+ */
73
86
  export type Strategy = 'seq' | 'first' | 'parallel'
74
87
 
75
- export type StrategySwitch<TStrategy extends Strategy, TInput extends Array<PromiseFunc<TValue, null>>, TValue> = TStrategy extends 'first'
76
- ? HookFirstOutput<TInput, TValue>
77
- : TStrategy extends 'seq'
78
- ? SeqOutput<TInput, TValue>
79
- : TStrategy extends 'parallel'
80
- ? HookParallelOutput<TInput, TValue>
81
- : never
88
+ type StrategyOutputMap<TInput extends Array<PromiseFunc<TValue, null>>, TValue> = {
89
+ first: HookFirstOutput<TInput, TValue>
90
+ seq: SeqOutput<TInput, TValue>
91
+ parallel: HookParallelOutput<TInput, TValue>
92
+ }
93
+
94
+ export type StrategySwitch<TStrategy extends Strategy, TInput extends Array<PromiseFunc<TValue, null>>, TValue> = StrategyOutputMap<TInput, TValue>[TStrategy]
@@ -4,18 +4,13 @@ import type { formatters } from '../constants.ts'
4
4
  type Formatter = keyof typeof formatters
5
5
 
6
6
  /**
7
- * Check if a formatter command is available in the system.
7
+ * Returns `true` when the given formatter is installed and callable.
8
8
  *
9
- * @param formatter - The formatter to check ('biome', 'prettier', or 'oxfmt')
10
- * @returns Promise that resolves to true if the formatter is available, false otherwise
11
- *
12
- * @remarks
13
- * This function checks availability by running `<formatter> --version` command.
14
- * All supported formatters (biome, prettier, oxfmt) implement the --version flag.
9
+ * Availability is detected by running `<formatter> --version` and checking
10
+ * that the process exits without error.
15
11
  */
16
12
  async function isFormatterAvailable(formatter: Formatter): Promise<boolean> {
17
13
  try {
18
- // Try to get the version of the formatter to check if it's installed
19
14
  await x(formatter, ['--version'], { nodeOptions: { stdio: 'ignore' } })
20
15
  return true
21
16
  } catch {
@@ -24,27 +19,21 @@ async function isFormatterAvailable(formatter: Formatter): Promise<boolean> {
24
19
  }
25
20
 
26
21
  /**
27
- * Detect which formatter is available in the system.
28
- *
29
- * @returns Promise that resolves to the first available formatter or undefined if none are found
22
+ * Detects the first available code formatter on the current system.
30
23
  *
31
- * @remarks
32
- * Checks in order of preference: biome, oxfmt, prettier.
33
- * Uses the `--version` flag to detect if each formatter command is available.
34
- * This is a reliable method as all supported formatters implement this flag.
24
+ * - Checks in preference order: `biome`, `oxfmt`, `prettier`.
25
+ * - Returns `null` when none are found.
35
26
  *
36
27
  * @example
37
- * ```typescript
28
+ * ```ts
38
29
  * const formatter = await detectFormatter()
39
30
  * if (formatter) {
40
31
  * console.log(`Using ${formatter} for formatting`)
41
- * } else {
42
- * console.log('No formatter found')
43
32
  * }
44
33
  * ```
45
34
  */
46
- export async function detectFormatter(): Promise<Formatter | undefined> {
47
- const formatterNames: Formatter[] = ['biome', 'oxfmt', 'prettier']
35
+ export async function detectFormatter(): Promise<Formatter | null> {
36
+ const formatterNames = new Set(['biome', 'oxfmt', 'prettier'] as const)
48
37
 
49
38
  for (const formatter of formatterNames) {
50
39
  if (await isFormatterAvailable(formatter)) {
@@ -52,5 +41,5 @@ export async function detectFormatter(): Promise<Formatter | undefined> {
52
41
  }
53
42
  }
54
43
 
55
- return undefined
44
+ return null
56
45
  }
@@ -1,7 +1,9 @@
1
+ /** biome-ignore-all lint/suspicious/useIterableCallbackReturn: not needed */
1
2
  import { join } from 'node:path'
3
+ import { getRelativePath } from '@internals/utils'
2
4
  import type { KubbFile } from '@kubb/fabric-core/types'
3
- import { BarrelManager } from '../BarrelManager.ts'
4
5
  import type { BarrelType } from '../types.ts'
6
+ import { TreeNode } from './TreeNode.ts'
5
7
 
6
8
  export type FileMetaBase = {
7
9
  pluginName?: string
@@ -27,6 +29,72 @@ type AddIndexesProps = {
27
29
  meta?: FileMetaBase
28
30
  }
29
31
 
32
+ function getBarrelFilesByRoot(root: string | undefined, files: Array<KubbFile.ResolvedFile>): Array<KubbFile.File> {
33
+ const cachedFiles = new Map<KubbFile.Path, KubbFile.File>()
34
+
35
+ TreeNode.build(files, root)?.forEach((treeNode) => {
36
+ if (!treeNode || !treeNode.children || !treeNode.parent?.data.path) {
37
+ return
38
+ }
39
+
40
+ const barrelFilePath = join(treeNode.parent?.data.path, 'index.ts') as KubbFile.Path
41
+ const barrelFile: KubbFile.File = {
42
+ path: barrelFilePath,
43
+ baseName: 'index.ts',
44
+ exports: [],
45
+ imports: [],
46
+ sources: [],
47
+ }
48
+ const previousBarrelFile = cachedFiles.get(barrelFile.path)
49
+ const leaves = treeNode.leaves
50
+
51
+ leaves.forEach((item) => {
52
+ if (!item.data.name) {
53
+ return
54
+ }
55
+
56
+ const sources = item.data.file?.sources || []
57
+
58
+ sources.forEach((source) => {
59
+ if (!item.data.file?.path || !source.isIndexable || !source.name) {
60
+ return
61
+ }
62
+ const alreadyContainInPreviousBarrelFile = previousBarrelFile?.sources.some(
63
+ (item) => item.name === source.name && item.isTypeOnly === source.isTypeOnly,
64
+ )
65
+
66
+ if (alreadyContainInPreviousBarrelFile) {
67
+ return
68
+ }
69
+
70
+ barrelFile.exports!.push({
71
+ name: [source.name],
72
+ path: getRelativePath(treeNode.parent?.data.path, item.data.path),
73
+ isTypeOnly: source.isTypeOnly,
74
+ })
75
+
76
+ barrelFile.sources.push({
77
+ name: source.name,
78
+ isTypeOnly: source.isTypeOnly,
79
+ //TODO use parser to generate import
80
+ value: '',
81
+ isExportable: false,
82
+ isIndexable: false,
83
+ })
84
+ })
85
+ })
86
+
87
+ if (previousBarrelFile) {
88
+ previousBarrelFile.sources.push(...barrelFile.sources)
89
+ previousBarrelFile.exports?.push(...(barrelFile.exports || []))
90
+ } else {
91
+ cachedFiles.set(barrelFile.path, barrelFile)
92
+ }
93
+ })
94
+
95
+ return [...cachedFiles.values()]
96
+ }
97
+
30
98
  function trimExtName(text: string): string {
31
99
  const dotIndex = text.lastIndexOf('.')
32
100
  // Only strip when the dot is found and no path separator follows it
@@ -37,24 +105,26 @@ function trimExtName(text: string): string {
37
105
  return text
38
106
  }
39
107
 
40
- export async function getBarrelFiles(files: Array<KubbFile.ResolvedFile>, { type, meta = {}, root, output }: AddIndexesProps): Promise<KubbFile.File[]> {
108
+ /**
109
+ * Generates `index.ts` barrel files for all directories under `root/output.path`.
110
+ *
111
+ * - Returns an empty array when `type` is falsy or `'propagate'`.
112
+ * - Skips generation when the output path itself ends with `index` (already a barrel).
113
+ * - When `type` is `'all'`, strips named exports so every re-export becomes a wildcard (`export * from`).
114
+ * - Attaches `meta` to each barrel file for downstream plugin identification.
115
+ */
116
+ export async function getBarrelFiles(files: Array<KubbFile.ResolvedFile>, { type, meta = {}, root, output }: AddIndexesProps): Promise<Array<KubbFile.File>> {
41
117
  if (!type || type === 'propagate') {
42
118
  return []
43
119
  }
44
120
 
45
- const barrelManager = new BarrelManager()
46
-
47
121
  const pathToBuildFrom = join(root, output.path)
48
122
 
49
123
  if (trimExtName(pathToBuildFrom).endsWith('index')) {
50
124
  return []
51
125
  }
52
126
 
53
- const barrelFiles = barrelManager.getFiles({
54
- files,
55
- root: pathToBuildFrom,
56
- meta,
57
- })
127
+ const barrelFiles = getBarrelFilesByRoot(pathToBuildFrom, files)
58
128
 
59
129
  if (type === 'all') {
60
130
  return barrelFiles.map((file) => {