@kubb/core 5.0.0-alpha.3 → 5.0.0-alpha.31

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 (60) hide show
  1. package/dist/PluginDriver-D0dY_hpJ.d.ts +1986 -0
  2. package/dist/{chunk-ByKO4r7w.cjs → chunk-MlS0t1Af.cjs} +15 -0
  3. package/dist/chunk-O_arW02_.js +17 -0
  4. package/dist/hooks.cjs +13 -28
  5. package/dist/hooks.cjs.map +1 -1
  6. package/dist/hooks.d.ts +11 -37
  7. package/dist/hooks.js +14 -28
  8. package/dist/hooks.js.map +1 -1
  9. package/dist/index.cjs +1469 -831
  10. package/dist/index.cjs.map +1 -1
  11. package/dist/index.d.ts +572 -191
  12. package/dist/index.js +1443 -826
  13. package/dist/index.js.map +1 -1
  14. package/package.json +7 -7
  15. package/src/Kubb.ts +38 -56
  16. package/src/KubbFile.ts +143 -0
  17. package/src/{PluginManager.ts → PluginDriver.ts} +159 -170
  18. package/src/build.ts +213 -65
  19. package/src/constants.ts +39 -6
  20. package/src/createAdapter.ts +25 -0
  21. package/src/createPlugin.ts +30 -0
  22. package/src/createStorage.ts +58 -0
  23. package/src/{config.ts → defineConfig.ts} +11 -16
  24. package/src/defineGenerator.ts +126 -0
  25. package/src/defineLogger.ts +13 -3
  26. package/src/defineParser.ts +57 -0
  27. package/src/definePresets.ts +16 -0
  28. package/src/defineResolver.ts +454 -0
  29. package/src/hooks/index.ts +1 -6
  30. package/src/hooks/useDriver.ts +11 -0
  31. package/src/hooks/useMode.ts +4 -4
  32. package/src/hooks/usePlugin.ts +3 -3
  33. package/src/index.ts +22 -10
  34. package/src/renderNode.tsx +25 -0
  35. package/src/storages/fsStorage.ts +2 -2
  36. package/src/storages/memoryStorage.ts +2 -2
  37. package/src/types.ts +639 -52
  38. package/src/utils/FunctionParams.ts +2 -2
  39. package/src/utils/TreeNode.ts +40 -2
  40. package/src/utils/diagnostics.ts +4 -1
  41. package/src/utils/executeStrategies.ts +29 -10
  42. package/src/utils/formatters.ts +10 -21
  43. package/src/utils/getBarrelFiles.ts +80 -10
  44. package/src/utils/getConfigs.ts +9 -23
  45. package/src/utils/getPreset.ts +78 -0
  46. package/src/utils/isInputPath.ts +8 -0
  47. package/src/utils/linters.ts +23 -3
  48. package/src/utils/packageJSON.ts +76 -0
  49. package/dist/chunk--u3MIqq1.js +0 -8
  50. package/dist/types-CiPWLv-5.d.ts +0 -1001
  51. package/src/BarrelManager.ts +0 -74
  52. package/src/PackageManager.ts +0 -180
  53. package/src/PromiseManager.ts +0 -40
  54. package/src/defineAdapter.ts +0 -22
  55. package/src/definePlugin.ts +0 -12
  56. package/src/defineStorage.ts +0 -56
  57. package/src/errors.ts +0 -1
  58. package/src/hooks/useKubb.ts +0 -22
  59. package/src/hooks/usePluginManager.ts +0 -11
  60. package/src/utils/getPlugins.ts +0 -23
package/src/build.ts CHANGED
@@ -1,41 +1,72 @@
1
1
  import { dirname, relative, resolve } from 'node:path'
2
- import { AsyncEventEmitter, exists, formatMs, getElapsedMs, getRelativePath, URLPath } from '@internals/utils'
3
- import type { KubbFile } from '@kubb/fabric-core/types'
4
- import type { Fabric } from '@kubb/react-fabric'
2
+ import { AsyncEventEmitter, BuildError, exists, formatMs, getElapsedMs, getRelativePath, URLPath } from '@internals/utils'
3
+ import { transform, walk } from '@kubb/ast'
4
+ import type { OperationNode } from '@kubb/ast/types'
5
+ import type { Fabric as FabricType } from '@kubb/fabric-core/types'
5
6
  import { createFabric } from '@kubb/react-fabric'
6
- import { typescriptParser } from '@kubb/react-fabric/parsers'
7
7
  import { fsPlugin } from '@kubb/react-fabric/plugins'
8
- import { isInputPath } from './config.ts'
9
8
  import { BARREL_FILENAME, DEFAULT_BANNER, DEFAULT_CONCURRENCY, DEFAULT_EXTENSION, DEFAULT_STUDIO_URL } from './constants.ts'
10
- import { BuildError } from './errors.ts'
11
- import { PluginManager } from './PluginManager.ts'
9
+ import { defineParser } from './defineParser.ts'
10
+ import type * as KubbFile from './KubbFile.ts'
11
+ import { PluginDriver } from './PluginDriver.ts'
12
+ import { applyHookResult } from './renderNode.tsx'
12
13
  import { fsStorage } from './storages/fsStorage.ts'
13
- import type { AdapterSource, Config, DefineStorage, KubbEvents, Output, Plugin, UserConfig } from './types.ts'
14
+ import type { AdapterSource, Config, KubbEvents, Plugin, PluginContext, Storage, UserConfig } from './types.ts'
14
15
  import { getDiagnosticInfo } from './utils/diagnostics.ts'
15
16
  import type { FileMetaBase } from './utils/getBarrelFiles.ts'
17
+ import { getBarrelFiles } from './utils/getBarrelFiles.ts'
18
+ import { isInputPath } from './utils/isInputPath.ts'
16
19
 
17
20
  type BuildOptions = {
18
21
  config: UserConfig
19
22
  events?: AsyncEventEmitter<KubbEvents>
20
23
  }
21
24
 
25
+ /**
26
+ * Full output produced by a successful or failed build.
27
+ */
22
28
  type BuildOutput = {
29
+ /**
30
+ * Plugins that threw during installation, paired with the caught error.
31
+ */
23
32
  failedPlugins: Set<{ plugin: Plugin; error: Error }>
24
- fabric: Fabric
33
+ fabric: FabricType
25
34
  files: Array<KubbFile.ResolvedFile>
26
- pluginManager: PluginManager
35
+ driver: PluginDriver
36
+ /**
37
+ * Elapsed time in milliseconds for each plugin, keyed by plugin name.
38
+ */
27
39
  pluginTimings: Map<string, number>
28
40
  error?: Error
41
+ /**
42
+ * Raw generated source, keyed by absolute file path.
43
+ */
29
44
  sources: Map<KubbFile.Path, string>
30
45
  }
31
46
 
47
+ /**
48
+ * Intermediate result returned by {@link setup} and accepted by {@link safeBuild}.
49
+ */
32
50
  type SetupResult = {
33
51
  events: AsyncEventEmitter<KubbEvents>
34
- fabric: Fabric
35
- pluginManager: PluginManager
52
+ fabric: FabricType
53
+ driver: PluginDriver
36
54
  sources: Map<KubbFile.Path, string>
55
+ config: Config
37
56
  }
38
57
 
58
+ /**
59
+ * Initializes all Kubb infrastructure for a build without executing any plugins.
60
+ *
61
+ * - Validates the input path (when applicable).
62
+ * - Applies config defaults (`root`, `output.*`, `devtools`).
63
+ * - Creates the Fabric instance and wires storage, format, and lint hooks.
64
+ * - Runs the adapter (if configured) to produce the universal `RootNode`.
65
+ * When no adapter is supplied and `@kubb/adapter-oas` is installed as an
66
+ *
67
+ * Pass the returned {@link SetupResult} directly to {@link safeBuild} or {@link build}
68
+ * via the `overrides` argument to reuse the same infrastructure across multiple runs.
69
+ */
39
70
  export async function setup(options: BuildOptions): Promise<SetupResult> {
40
71
  const { config: userConfig, events = new AsyncEventEmitter<KubbEvents>() } = options
41
72
 
@@ -87,9 +118,15 @@ export async function setup(options: BuildOptions): Promise<SetupResult> {
87
118
  }
88
119
  }
89
120
 
90
- const definedConfig: Config = {
121
+ if (!userConfig.adapter) {
122
+ throw new Error('Adapter should be defined')
123
+ }
124
+
125
+ const config: Config = {
91
126
  root: userConfig.root || process.cwd(),
92
127
  ...userConfig,
128
+ parsers: userConfig.parsers ?? [],
129
+ adapter: userConfig.adapter,
93
130
  output: {
94
131
  write: true,
95
132
  barrelType: 'named',
@@ -110,19 +147,35 @@ export async function setup(options: BuildOptions): Promise<SetupResult> {
110
147
  // storage or fall back to fsStorage (backwards-compatible default).
111
148
  // Keys are root-relative (e.g. `src/gen/api/getPets.ts`) so fsStorage()
112
149
  // needs no configuration — it resolves them against process.cwd().
113
- const storage: DefineStorage | null = definedConfig.output.write === false ? null : (definedConfig.output.storage ?? fsStorage())
150
+ const storage: Storage | null = config.output.write === false ? null : (config.output.storage ?? fsStorage())
114
151
 
115
- if (definedConfig.output.clean) {
152
+ if (config.output.clean) {
116
153
  await events.emit('debug', {
117
154
  date: new Date(),
118
- logs: ['Cleaning output directories', ` • Output: ${definedConfig.output.path}`],
155
+ logs: ['Cleaning output directories', ` • Output: ${config.output.path}`],
119
156
  })
120
- await storage?.clear(resolve(definedConfig.root, definedConfig.output.path))
157
+ await storage?.clear(resolve(config.root, config.output.path))
121
158
  }
122
159
 
123
160
  const fabric = createFabric()
124
161
  fabric.use(fsPlugin)
125
- fabric.use(typescriptParser)
162
+
163
+ for (const parser of config.parsers) {
164
+ fabric.use(parser)
165
+ }
166
+ // Catch-all fallback: joins all source values for any unhandled extension
167
+ fabric.use(
168
+ defineParser({
169
+ name: 'fallback',
170
+ extNames: undefined,
171
+ parse(file) {
172
+ return file.sources
173
+ .map((item) => item.value)
174
+ .filter((value): value is string => value != null)
175
+ .join('\n\n')
176
+ },
177
+ }),
178
+ )
126
179
 
127
180
  fabric.context.on('files:processing:start', (files) => {
128
181
  events.emit('files:processing:start', files)
@@ -136,13 +189,13 @@ export async function setup(options: BuildOptions): Promise<SetupResult> {
136
189
  const { file, source } = params
137
190
  await events.emit('file:processing:update', {
138
191
  ...params,
139
- config: definedConfig,
192
+ config: config,
140
193
  source,
141
194
  })
142
195
 
143
196
  if (source) {
144
197
  // Key is root-relative so it's meaningful for any backend (fs, S3, Redis…)
145
- const key = relative(resolve(definedConfig.root), file.path)
198
+ const key = relative(resolve(config.root), file.path)
146
199
  await storage?.setItem(key, source)
147
200
  sources.set(file.path, source)
148
201
  }
@@ -158,51 +211,57 @@ export async function setup(options: BuildOptions): Promise<SetupResult> {
158
211
 
159
212
  await events.emit('debug', {
160
213
  date: new Date(),
161
- logs: [
162
- '✓ Fabric initialized',
163
- ` • Storage: ${storage ? storage.name : 'disabled (dry-run)'}`,
164
- ` • Barrel type: ${definedConfig.output.barrelType || 'none'}`,
165
- ],
214
+ logs: ['✓ Fabric initialized', ` • Storage: ${storage ? storage.name : 'disabled (dry-run)'}`, ` • Barrel type: ${config.output.barrelType || 'none'}`],
166
215
  })
167
216
 
168
- const pluginManager = new PluginManager(definedConfig, {
217
+ const driver = new PluginDriver(config, {
169
218
  fabric,
170
219
  events,
171
220
  concurrency: DEFAULT_CONCURRENCY,
172
221
  })
173
222
 
174
- // Run the adapter (if provided) to produce the universal RootNode
175
- if (definedConfig.adapter) {
176
- const source = inputToAdapterSource(definedConfig)
223
+ // Run the adapter to produce the universal RootNode.
177
224
 
178
- await events.emit('debug', {
179
- date: new Date(),
180
- logs: [`Running adapter: ${definedConfig.adapter.name}`],
181
- })
225
+ const adapter = config.adapter
226
+ if (!adapter) {
227
+ throw new Error('No adapter configured. Please provide an adapter in your kubb.config.ts.')
228
+ }
229
+ const source = inputToAdapterSource(config)
182
230
 
183
- pluginManager.adapter = definedConfig.adapter
184
- pluginManager.rootNode = await definedConfig.adapter.parse(source)
231
+ await events.emit('debug', {
232
+ date: new Date(),
233
+ logs: [`Running adapter: ${adapter.name}`],
234
+ })
185
235
 
186
- await events.emit('debug', {
187
- date: new Date(),
188
- logs: [
189
- `✓ Adapter '${definedConfig.adapter.name}' resolved RootNode`,
190
- ` • Schemas: ${pluginManager.rootNode.schemas.length}`,
191
- ` • Operations: ${pluginManager.rootNode.operations.length}`,
192
- ],
193
- })
194
- }
236
+ driver.adapter = adapter
237
+ driver.rootNode = await adapter.parse(source)
238
+
239
+ await events.emit('debug', {
240
+ date: new Date(),
241
+ logs: [
242
+ `✓ Adapter '${adapter.name}' resolved RootNode`,
243
+ ` • Schemas: ${driver.rootNode.schemas.length}`,
244
+ ` • Operations: ${driver.rootNode.operations.length}`,
245
+ ],
246
+ })
195
247
 
196
248
  return {
249
+ config,
197
250
  events,
198
251
  fabric,
199
- pluginManager,
252
+ driver,
200
253
  sources,
201
254
  }
202
255
  }
203
256
 
257
+ /**
258
+ * Runs a full Kubb build and throws on any error or plugin failure.
259
+ *
260
+ * Internally delegates to {@link safeBuild} and rethrows collected errors.
261
+ * Pass an existing {@link SetupResult} via `overrides` to skip the setup phase.
262
+ */
204
263
  export async function build(options: BuildOptions, overrides?: SetupResult): Promise<BuildOutput> {
205
- const { fabric, files, pluginManager, failedPlugins, pluginTimings, error, sources } = await safeBuild(options, overrides)
264
+ const { fabric, files, driver, failedPlugins, pluginTimings, error, sources } = await safeBuild(options, overrides)
206
265
 
207
266
  if (error) {
208
267
  throw error
@@ -218,27 +277,88 @@ export async function build(options: BuildOptions, overrides?: SetupResult): Pro
218
277
  failedPlugins,
219
278
  fabric,
220
279
  files,
221
- pluginManager,
280
+ driver,
222
281
  pluginTimings,
223
282
  error: undefined,
224
283
  sources,
225
284
  }
226
285
  }
227
286
 
287
+ /**
288
+ * Walks the AST and dispatches nodes to a plugin's direct AST hooks
289
+ * (`schema`, `operation`, `operations`).
290
+ *
291
+ * - Each hook accepts a single handler **or an array** — all entries are called in sequence.
292
+ * - Nodes that are excluded by `exclude`/`include` plugin options are skipped automatically.
293
+ * - Return values are handled via `applyHookResult`: React elements are rendered,
294
+ * `KubbFile.File[]` are written via upsert, and `void` is a no-op (manual handling).
295
+ * - Barrel files are generated automatically when `output.barrelType` is set.
296
+ */
297
+ async function runPluginAstHooks(plugin: Plugin, context: PluginContext): Promise<void> {
298
+ const { adapter, rootNode, resolver, fabric } = context
299
+ const { exclude, include, override } = plugin.options
300
+
301
+ if (!adapter || !rootNode) {
302
+ throw new Error(`[${plugin.name}] No adapter found. Add an OAS adapter (e.g. pluginOas()) before this plugin in your Kubb config.`)
303
+ }
304
+
305
+ const collectedOperations: Array<OperationNode> = []
306
+
307
+ await walk(rootNode, {
308
+ depth: 'shallow',
309
+ async schema(node) {
310
+ if (!plugin.schema) return
311
+ const transformedNode = plugin.transformer ? transform(node, plugin.transformer) : node
312
+ const options = resolver.resolveOptions(transformedNode, { options: plugin.options, exclude, include, override })
313
+ if (options === null) return
314
+ const result = await plugin.schema.call(context, transformedNode, options)
315
+
316
+ await applyHookResult(result, fabric)
317
+ },
318
+ async operation(node) {
319
+ const transformedNode = plugin.transformer ? transform(node, plugin.transformer) : node
320
+ const options = resolver.resolveOptions(transformedNode, { options: plugin.options, exclude, include, override })
321
+ if (options !== null) {
322
+ collectedOperations.push(transformedNode)
323
+ if (plugin.operation) {
324
+ const result = await plugin.operation.call(context, transformedNode, options)
325
+ await applyHookResult(result, fabric)
326
+ }
327
+ }
328
+ },
329
+ })
330
+
331
+ if (plugin.operations && collectedOperations.length > 0) {
332
+ const result = await plugin.operations.call(context, collectedOperations, plugin.options)
333
+
334
+ await applyHookResult(result, fabric)
335
+ }
336
+ }
337
+
338
+ /**
339
+ * Runs a full Kubb build and captures errors instead of throwing.
340
+ *
341
+ * - Installs each plugin in order, recording failures in `failedPlugins`.
342
+ * - Generates the root barrel file when `output.barrelType` is set.
343
+ * - Writes all files through Fabric.
344
+ *
345
+ * Returns a {@link BuildOutput} even on failure — inspect `error` and
346
+ * `failedPlugins` to determine whether the build succeeded.
347
+ */
228
348
  export async function safeBuild(options: BuildOptions, overrides?: SetupResult): Promise<BuildOutput> {
229
- const { fabric, pluginManager, events, sources } = overrides ? overrides : await setup(options)
349
+ const { fabric, driver, events, sources } = overrides ? overrides : await setup(options)
230
350
 
231
351
  const failedPlugins = new Set<{ plugin: Plugin; error: Error }>()
232
352
  // in ms
233
353
  const pluginTimings = new Map<string, number>()
234
- const config = pluginManager.config
354
+ const config = driver.config
235
355
 
236
356
  try {
237
- for (const plugin of pluginManager.plugins) {
238
- const context = pluginManager.getContext(plugin)
357
+ for (const plugin of driver.plugins.values()) {
358
+ const context = driver.getContext(plugin)
239
359
  const hrStart = process.hrtime()
240
-
241
- const installer = plugin.install.bind(context)
360
+ const { output } = plugin.options ?? {}
361
+ const root = resolve(config.root, config.output.path)
242
362
 
243
363
  try {
244
364
  const timestamp = new Date()
@@ -247,10 +367,26 @@ export async function safeBuild(options: BuildOptions, overrides?: SetupResult):
247
367
 
248
368
  await events.emit('debug', {
249
369
  date: timestamp,
250
- logs: ['Installing plugin...', ` • Plugin Name: ${plugin.name}`],
370
+ logs: ['Starting plugin...', ` • Plugin Name: ${plugin.name}`],
251
371
  })
252
372
 
253
- await installer(context)
373
+ // Call buildStart() for any custom plugin logic
374
+ await plugin.buildStart.call(context)
375
+
376
+ // Dispatch schema/operation/operations hooks (direct hooks or composed via composeGenerators)
377
+ if (plugin.schema || plugin.operation || plugin.operations) {
378
+ await runPluginAstHooks(plugin, context)
379
+ }
380
+
381
+ if (output) {
382
+ const barrelFiles = await getBarrelFiles(fabric.files, {
383
+ type: output.barrelType ?? 'named',
384
+ root,
385
+ output,
386
+ meta: { pluginName: plugin.name },
387
+ })
388
+ await context.upsertFile(...barrelFiles)
389
+ }
254
390
 
255
391
  const duration = getElapsedMs(hrStart)
256
392
  pluginTimings.set(plugin.name, duration)
@@ -259,7 +395,7 @@ export async function safeBuild(options: BuildOptions, overrides?: SetupResult):
259
395
 
260
396
  await events.emit('debug', {
261
397
  date: new Date(),
262
- logs: [`✓ Plugin installed successfully (${formatMs(duration)})`],
398
+ logs: [`✓ Plugin started successfully (${formatMs(duration)})`],
263
399
  })
264
400
  } catch (caughtError) {
265
401
  const error = caughtError as Error
@@ -275,7 +411,7 @@ export async function safeBuild(options: BuildOptions, overrides?: SetupResult):
275
411
  await events.emit('debug', {
276
412
  date: errorTimestamp,
277
413
  logs: [
278
- '✗ Plugin installation failed',
414
+ '✗ Plugin start failed',
279
415
  ` • Plugin Name: ${plugin.name}`,
280
416
  ` • Error: ${error.constructor.name} - ${error.message}`,
281
417
  ' • Stack Trace:',
@@ -314,7 +450,7 @@ export async function safeBuild(options: BuildOptions, overrides?: SetupResult):
314
450
  const rootFile: KubbFile.File = {
315
451
  path: rootPath,
316
452
  baseName: BARREL_FILENAME,
317
- exports: buildBarrelExports({ barrelFiles, rootDir, existingExports, config, pluginManager }),
453
+ exports: buildBarrelExports({ barrelFiles, rootDir, existingExports, config, driver }),
318
454
  sources: [],
319
455
  imports: [],
320
456
  meta: {},
@@ -332,11 +468,19 @@ export async function safeBuild(options: BuildOptions, overrides?: SetupResult):
332
468
 
333
469
  await fabric.write({ extension: config.output.extension })
334
470
 
471
+ // Call buildEnd() on each plugin after all files are written
472
+ for (const plugin of driver.plugins.values()) {
473
+ if (plugin.buildEnd) {
474
+ const context = driver.getContext(plugin)
475
+ await plugin.buildEnd.call(context)
476
+ }
477
+ }
478
+
335
479
  return {
336
480
  failedPlugins,
337
481
  fabric,
338
482
  files,
339
- pluginManager,
483
+ driver,
340
484
  pluginTimings,
341
485
  sources,
342
486
  }
@@ -345,7 +489,7 @@ export async function safeBuild(options: BuildOptions, overrides?: SetupResult):
345
489
  failedPlugins,
346
490
  fabric,
347
491
  files: [],
348
- pluginManager,
492
+ driver,
349
493
  pluginTimings,
350
494
  error: error as Error,
351
495
  sources,
@@ -358,12 +502,12 @@ type BuildBarrelExportsParams = {
358
502
  rootDir: string
359
503
  existingExports: Set<string>
360
504
  config: Config
361
- pluginManager: PluginManager
505
+ driver: PluginDriver
362
506
  }
363
507
 
364
- function buildBarrelExports({ barrelFiles, rootDir, existingExports, config, pluginManager }: BuildBarrelExportsParams): KubbFile.Export[] {
508
+ function buildBarrelExports({ barrelFiles, rootDir, existingExports, config, driver }: BuildBarrelExportsParams): KubbFile.Export[] {
365
509
  const pluginNameMap = new Map<string, Plugin>()
366
- for (const plugin of pluginManager.plugins) {
510
+ for (const plugin of driver.plugins.values()) {
367
511
  pluginNameMap.set(plugin.name, plugin)
368
512
  }
369
513
 
@@ -377,7 +521,7 @@ function buildBarrelExports({ barrelFiles, rootDir, existingExports, config, plu
377
521
 
378
522
  const meta = file.meta as FileMetaBase | undefined
379
523
  const plugin = meta?.pluginName ? pluginNameMap.get(meta.pluginName) : undefined
380
- const pluginOptions = plugin?.options as { output?: Output<unknown> } | undefined
524
+ const pluginOptions = plugin?.options
381
525
 
382
526
  if (!pluginOptions || pluginOptions.output?.barrelType === false) {
383
527
  return []
@@ -407,7 +551,7 @@ function inputToAdapterSource(config: Config): AdapterSource {
407
551
  if (Array.isArray(config.input)) {
408
552
  return {
409
553
  type: 'paths',
410
- paths: config.input.map((i) => resolve(config.root, i.path)),
554
+ paths: config.input.map((i) => (new URLPath(i.path).isURL ? i.path : resolve(config.root, i.path))),
411
555
  }
412
556
  }
413
557
 
@@ -415,6 +559,10 @@ function inputToAdapterSource(config: Config): AdapterSource {
415
559
  return { type: 'data', data: config.input.data }
416
560
  }
417
561
 
562
+ if (new URLPath(config.input.path).isURL) {
563
+ return { type: 'path', path: config.input.path }
564
+ }
565
+
418
566
  const resolved = resolve(config.root, config.input.path)
419
567
  return { type: 'path', path: resolved }
420
568
  }
package/src/constants.ts CHANGED
@@ -1,21 +1,40 @@
1
- import type { KubbFile } from '@kubb/fabric-core/types'
1
+ import type * as KubbFile from './KubbFile.ts'
2
2
 
3
+ /**
4
+ * Base URL for the Kubb Studio web app.
5
+ */
3
6
  export const DEFAULT_STUDIO_URL = 'https://studio.kubb.dev' as const
4
7
 
5
- export const CORE_PLUGIN_NAME = 'core' as const
6
-
7
- export const DEFAULT_MAX_LISTENERS = 100
8
-
8
+ /**
9
+ * Default number of plugins that may run concurrently during a build.
10
+ */
9
11
  export const DEFAULT_CONCURRENCY = 15
10
12
 
13
+ /**
14
+ * File name used for generated barrel (index) files.
15
+ */
11
16
  export const BARREL_FILENAME = 'index.ts' as const
12
17
 
18
+ /**
19
+ * Default banner style written at the top of every generated file.
20
+ */
13
21
  export const DEFAULT_BANNER = 'simple' as const
14
22
 
23
+ /**
24
+ * Default file-extension mapping used when no explicit mapping is configured.
25
+ */
15
26
  export const DEFAULT_EXTENSION: Record<KubbFile.Extname, KubbFile.Extname | ''> = { '.ts': '.ts' }
16
27
 
17
- export const PATH_SEPARATORS = ['/', '\\'] as const
28
+ /**
29
+ * Characters recognized as path separators on both POSIX and Windows.
30
+ */
31
+ export const PATH_SEPARATORS = new Set(['/', '\\'] as const)
18
32
 
33
+ /**
34
+ * Numeric log-level thresholds used internally to compare verbosity.
35
+ *
36
+ * Higher numbers are more verbose.
37
+ */
19
38
  export const logLevel = {
20
39
  silent: Number.NEGATIVE_INFINITY,
21
40
  error: 0,
@@ -25,6 +44,13 @@ export const logLevel = {
25
44
  debug: 5,
26
45
  } as const
27
46
 
47
+ /**
48
+ * CLI command descriptors for each supported linter.
49
+ *
50
+ * Each entry contains the executable `command`, an `args` factory that maps an
51
+ * output path to the correct argument list, and an `errorMessage` shown when
52
+ * the linter is not found.
53
+ */
28
54
  export const linters = {
29
55
  eslint: {
30
56
  command: 'eslint',
@@ -43,6 +69,13 @@ export const linters = {
43
69
  },
44
70
  } as const
45
71
 
72
+ /**
73
+ * CLI command descriptors for each supported code formatter.
74
+ *
75
+ * Each entry contains the executable `command`, an `args` factory that maps an
76
+ * output path to the correct argument list, and an `errorMessage` shown when
77
+ * the formatter is not found.
78
+ */
46
79
  export const formatters = {
47
80
  prettier: {
48
81
  command: 'prettier',
@@ -0,0 +1,25 @@
1
+ import type { Adapter, AdapterFactoryOptions } from './types.ts'
2
+
3
+ /**
4
+ * Builder type for an {@link Adapter} — takes options and returns the adapter instance.
5
+ */
6
+ type AdapterBuilder<T extends AdapterFactoryOptions> = (options: T['options']) => Adapter<T>
7
+
8
+ /**
9
+ * Creates an adapter factory. Call the returned function with optional options to get the adapter instance.
10
+ *
11
+ * @example
12
+ * export const myAdapter = createAdapter<MyAdapter>((options) => {
13
+ * return {
14
+ * name: 'my-adapter',
15
+ * options,
16
+ * async parse(source) { ... },
17
+ * }
18
+ * })
19
+ *
20
+ * // instantiate
21
+ * const adapter = myAdapter({ validate: true })
22
+ */
23
+ export function createAdapter<T extends AdapterFactoryOptions = AdapterFactoryOptions>(build: AdapterBuilder<T>): (options?: T['options']) => Adapter<T> {
24
+ return (options) => build(options ?? ({} as T['options']))
25
+ }
@@ -0,0 +1,30 @@
1
+ import type { PluginFactoryOptions, UserPluginWithLifeCycle } from './types.ts'
2
+
3
+ /**
4
+ * Builder type for a {@link UserPluginWithLifeCycle} — takes options and returns the plugin instance.
5
+ */
6
+ type PluginBuilder<T extends PluginFactoryOptions = PluginFactoryOptions> = (options: T['options']) => UserPluginWithLifeCycle<T>
7
+
8
+ /**
9
+ * Creates a plugin factory. Call the returned function with optional options to get the plugin instance.
10
+ *
11
+ * @example
12
+ * ```ts
13
+ * export const myPlugin = createPlugin<MyPlugin>((options) => {
14
+ * return {
15
+ * name: 'my-plugin',
16
+ * get options() { return options },
17
+ * resolvePath(baseName) { ... },
18
+ * resolveName(name, type) { ... },
19
+ * }
20
+ * })
21
+ *
22
+ * // instantiate
23
+ * const plugin = myPlugin({ output: { path: 'src/gen' } })
24
+ * ```
25
+ */
26
+ export function createPlugin<T extends PluginFactoryOptions = PluginFactoryOptions>(
27
+ build: PluginBuilder<T>,
28
+ ): (options?: T['options']) => UserPluginWithLifeCycle<T> {
29
+ return (options) => build(options ?? ({} as T['options']))
30
+ }
@@ -0,0 +1,58 @@
1
+ export type Storage = {
2
+ /**
3
+ * Identifier used for logging and debugging (e.g. `'fs'`, `'s3'`).
4
+ */
5
+ readonly name: string
6
+ /**
7
+ * Returns `true` when an entry for `key` exists in storage.
8
+ */
9
+ hasItem(key: string): Promise<boolean>
10
+ /**
11
+ * Returns the stored string value, or `null` when `key` does not exist.
12
+ */
13
+ getItem(key: string): Promise<string | null>
14
+ /**
15
+ * Persists `value` under `key`, creating any required structure.
16
+ */
17
+ setItem(key: string, value: string): Promise<void>
18
+ /**
19
+ * Removes the entry for `key`. No-ops when the key does not exist.
20
+ */
21
+ removeItem(key: string): Promise<void>
22
+ /**
23
+ * Returns all keys, optionally filtered to those starting with `base`.
24
+ */
25
+ getKeys(base?: string): Promise<Array<string>>
26
+ /**
27
+ * Removes all entries, optionally scoped to those starting with `base`.
28
+ */
29
+ clear(base?: string): Promise<void>
30
+ /**
31
+ * Optional teardown hook called after the build completes.
32
+ */
33
+ dispose?(): Promise<void>
34
+ }
35
+
36
+ /**
37
+ * Creates a storage factory. Call the returned function with optional options to get the storage instance.
38
+ *
39
+ * @example
40
+ * export const memoryStorage = createStorage(() => {
41
+ * const store = new Map<string, string>()
42
+ * return {
43
+ * name: 'memory',
44
+ * async hasItem(key) { return store.has(key) },
45
+ * async getItem(key) { return store.get(key) ?? null },
46
+ * async setItem(key, value) { store.set(key, value) },
47
+ * async removeItem(key) { store.delete(key) },
48
+ * async getKeys(base) {
49
+ * const keys = [...store.keys()]
50
+ * return base ? keys.filter((k) => k.startsWith(base)) : keys
51
+ * },
52
+ * async clear(base) { if (!base) store.clear() },
53
+ * }
54
+ * })
55
+ */
56
+ export function createStorage<TOptions = Record<string, never>>(build: (options: TOptions) => Storage): (options?: TOptions) => Storage {
57
+ return (options) => build(options ?? ({} as TOptions))
58
+ }