@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.
- package/dist/{types-B7eZvqwD.d.ts → PluginDriver-BkSenc-R.d.ts} +521 -299
- package/dist/hooks.cjs +101 -8
- package/dist/hooks.cjs.map +1 -1
- package/dist/hooks.d.ts +83 -4
- package/dist/hooks.js +99 -8
- package/dist/hooks.js.map +1 -1
- package/dist/index.cjs +850 -536
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +438 -89
- package/dist/index.js +839 -532
- package/dist/index.js.map +1 -1
- package/package.json +6 -6
- package/src/Kubb.ts +37 -55
- package/src/{PluginManager.ts → PluginDriver.ts} +51 -40
- package/src/build.ts +74 -29
- package/src/config.ts +9 -8
- package/src/constants.ts +44 -1
- package/src/createAdapter.ts +25 -0
- package/src/createPlugin.ts +28 -0
- package/src/createStorage.ts +58 -0
- package/src/defineGenerator.ts +134 -0
- package/src/defineLogger.ts +13 -3
- package/src/definePreset.ts +23 -0
- package/src/definePresets.ts +16 -0
- package/src/defineResolver.ts +131 -0
- package/src/hooks/index.ts +2 -1
- package/src/hooks/useKubb.ts +160 -0
- package/src/hooks/useMode.ts +5 -2
- package/src/hooks/usePlugin.ts +5 -2
- package/src/hooks/usePluginDriver.ts +11 -0
- package/src/index.ts +12 -6
- package/src/renderNode.tsx +108 -0
- package/src/storages/fsStorage.ts +2 -2
- package/src/storages/memoryStorage.ts +2 -2
- package/src/types.ts +150 -38
- package/src/utils/FunctionParams.ts +2 -2
- package/src/utils/TreeNode.ts +24 -1
- package/src/utils/diagnostics.ts +4 -1
- package/src/utils/executeStrategies.ts +23 -10
- package/src/utils/formatters.ts +10 -21
- package/src/utils/getBarrelFiles.ts +79 -9
- package/src/utils/getConfigs.ts +8 -22
- package/src/utils/getPreset.ts +41 -0
- package/src/utils/linters.ts +23 -3
- package/src/utils/mergeResolvers.ts +8 -0
- package/src/utils/packageJSON.ts +76 -0
- package/src/BarrelManager.ts +0 -74
- package/src/PackageManager.ts +0 -180
- package/src/PromiseManager.ts +0 -40
- package/src/defineAdapter.ts +0 -22
- package/src/definePlugin.ts +0 -12
- package/src/defineStorage.ts +0 -56
- package/src/errors.ts +0 -1
- package/src/hooks/usePluginManager.ts +0 -8
- 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 {
|
|
5
|
+
import type { Storage } from './createStorage.ts'
|
|
7
6
|
import type { KubbEvents } from './Kubb.ts'
|
|
8
|
-
import type {
|
|
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
|
|
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<
|
|
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
|
-
/**
|
|
101
|
+
/**
|
|
102
|
+
* Human-readable identifier, e.g. `'oas'`, `'drizzle'`, `'asyncapi'`.
|
|
103
|
+
*/
|
|
96
104
|
name: TOptions['name']
|
|
97
|
-
/**
|
|
105
|
+
/**
|
|
106
|
+
* Resolved options (after defaults have been applied).
|
|
107
|
+
*/
|
|
98
108
|
options: TOptions['resolvedOptions']
|
|
99
|
-
/**
|
|
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
|
|
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 {
|
|
174
|
-
* storage:
|
|
198
|
+
* import { memoryStorage } from '@kubb/core'
|
|
199
|
+
* storage: memoryStorage()
|
|
175
200
|
* ```
|
|
176
201
|
*/
|
|
177
|
-
storage?:
|
|
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
|
-
|
|
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
|
-
*
|
|
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:
|
|
462
|
+
fabric: FabricType
|
|
395
463
|
config: Config
|
|
396
|
-
|
|
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
|
-
} &
|
|
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:
|
|
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
|
|
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:
|
|
560
|
+
install: (context: LoggerContext, options?: TOptions) => void | Promise<void>
|
|
486
561
|
}
|
|
487
562
|
|
|
488
|
-
export type UserLogger<TOptions extends LoggerOptions = LoggerOptions> =
|
|
563
|
+
export type UserLogger<TOptions extends LoggerOptions = LoggerOptions> = Logger<TOptions>
|
|
489
564
|
|
|
490
|
-
|
|
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[]> = []
|
package/src/utils/TreeNode.ts
CHANGED
|
@@ -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 '../
|
|
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)
|
package/src/utils/diagnostics.ts
CHANGED
|
@@ -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
|
-
*
|
|
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
|
-
*
|
|
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
|
-
*
|
|
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
|
|
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
|
-
|
|
76
|
-
|
|
77
|
-
:
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
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]
|
package/src/utils/formatters.ts
CHANGED
|
@@ -4,18 +4,13 @@ import type { formatters } from '../constants.ts'
|
|
|
4
4
|
type Formatter = keyof typeof formatters
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
|
-
*
|
|
7
|
+
* Returns `true` when the given formatter is installed and callable.
|
|
8
8
|
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
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
|
-
*
|
|
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
|
-
*
|
|
32
|
-
*
|
|
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
|
-
* ```
|
|
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 |
|
|
47
|
-
const formatterNames
|
|
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
|
|
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
|
-
|
|
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 =
|
|
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) => {
|