@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/dist/index.js CHANGED
@@ -1,29 +1,37 @@
1
1
  import "./chunk--u3MIqq1.js";
2
- import mod from "node:module";
3
2
  import { EventEmitter } from "node:events";
4
- import { parseArgs, styleText } from "node:util";
5
3
  import { readFileSync } from "node:fs";
6
4
  import { access, mkdir, readFile, readdir, rm, writeFile } from "node:fs/promises";
7
- import path, { dirname, join, posix, relative, resolve } from "node:path";
8
- import { definePrinter } from "@kubb/ast";
9
- import { createFabric } from "@kubb/react-fabric";
5
+ import path, { basename, dirname, extname, join, posix, relative, resolve } from "node:path";
6
+ import { definePrinter, isOperationNode, isSchemaNode } from "@kubb/ast";
7
+ import { Fabric, createFabric, createReactFabric } from "@kubb/react-fabric";
10
8
  import { typescriptParser } from "@kubb/react-fabric/parsers";
11
9
  import { fsPlugin } from "@kubb/react-fabric/plugins";
12
10
  import { performance } from "node:perf_hooks";
13
11
  import { deflateSync } from "fflate";
14
12
  import { x } from "tinyexec";
15
13
  import { version } from "node:process";
16
- import os from "node:os";
17
- import { pathToFileURL } from "node:url";
14
+ import { jsx } from "@kubb/react-fabric/jsx-runtime";
15
+ import { sortBy } from "remeda";
18
16
  import * as pkg from "empathic/package";
19
17
  import { coerce, satisfies } from "semver";
20
- import { sortBy } from "remeda";
21
- //#region ../../internals/utils/dist/index.js
22
- /** Thrown when a plugin's configuration or input fails validation. */
18
+ //#region ../../internals/utils/src/errors.ts
19
+ /** Thrown when a plugin's configuration or input fails validation.
20
+ *
21
+ * @example
22
+ * ```ts
23
+ * throw new ValidationPluginError('Invalid config: "output.path" is required')
24
+ * ```
25
+ */
23
26
  var ValidationPluginError = class extends Error {};
24
27
  /**
25
28
  * Thrown when one or more errors occur during a Kubb build.
26
29
  * Carries the full list of underlying errors on `errors`.
30
+ *
31
+ * @example
32
+ * ```ts
33
+ * throw new BuildError('Build failed', { errors: [err1, err2] })
34
+ * ```
27
35
  */
28
36
  var BuildError = class extends Error {
29
37
  errors;
@@ -35,19 +43,34 @@ var BuildError = class extends Error {
35
43
  };
36
44
  /**
37
45
  * Coerces an unknown thrown value to an `Error` instance.
38
- * When the value is already an `Error` it is returned as-is;
39
- * otherwise a new `Error` is created whose message is `String(value)`.
46
+ * Returns the value as-is when it is already an `Error`; otherwise wraps it with `String(value)`.
47
+ *
48
+ * @example
49
+ * ```ts
50
+ * try { ... } catch(err) {
51
+ * throw new BuildError('Build failed', { cause: toError(err), errors: [] })
52
+ * }
53
+ * ```
40
54
  */
41
55
  function toError(value) {
42
56
  return value instanceof Error ? value : new Error(String(value));
43
57
  }
58
+ //#endregion
59
+ //#region ../../internals/utils/src/asyncEventEmitter.ts
44
60
  /**
45
- * A typed EventEmitter that awaits all async listeners before resolving.
61
+ * Typed `EventEmitter` that awaits all async listeners before resolving.
46
62
  * Wraps Node's `EventEmitter` with full TypeScript event-map inference.
63
+ *
64
+ * @example
65
+ * ```ts
66
+ * const emitter = new AsyncEventEmitter<{ build: [name: string] }>()
67
+ * emitter.on('build', async (name) => { console.log(name) })
68
+ * await emitter.emit('build', 'petstore') // all listeners awaited
69
+ * ```
47
70
  */
48
71
  var AsyncEventEmitter = class {
49
72
  /**
50
- * `maxListener` controls the maximum number of listeners per event before Node emits a memory-leak warning.
73
+ * Maximum number of listeners per event before Node emits a memory-leak warning.
51
74
  * @default 10
52
75
  */
53
76
  constructor(maxListener = 10) {
@@ -55,8 +78,13 @@ var AsyncEventEmitter = class {
55
78
  }
56
79
  #emitter = new EventEmitter();
57
80
  /**
58
- * Emits an event and awaits all registered listeners in parallel.
81
+ * Emits `eventName` and awaits all registered listeners in parallel.
59
82
  * Throws if any listener rejects, wrapping the cause with the event name and serialized arguments.
83
+ *
84
+ * @example
85
+ * ```ts
86
+ * await emitter.emit('build', 'petstore')
87
+ * ```
60
88
  */
61
89
  async emit(eventName, ...eventArgs) {
62
90
  const listeners = this.#emitter.listeners(eventName);
@@ -75,11 +103,25 @@ var AsyncEventEmitter = class {
75
103
  }
76
104
  }));
77
105
  }
78
- /** Registers a persistent listener for the given event. */
106
+ /**
107
+ * Registers a persistent listener for `eventName`.
108
+ *
109
+ * @example
110
+ * ```ts
111
+ * emitter.on('build', async (name) => { console.log(name) })
112
+ * ```
113
+ */
79
114
  on(eventName, handler) {
80
115
  this.#emitter.on(eventName, handler);
81
116
  }
82
- /** Registers a one-shot listener that removes itself after the first invocation. */
117
+ /**
118
+ * Registers a one-shot listener that removes itself after the first invocation.
119
+ *
120
+ * @example
121
+ * ```ts
122
+ * emitter.onOnce('build', async (name) => { console.log(name) })
123
+ * ```
124
+ */
83
125
  onOnce(eventName, handler) {
84
126
  const wrapper = (...args) => {
85
127
  this.off(eventName, wrapper);
@@ -87,15 +129,31 @@ var AsyncEventEmitter = class {
87
129
  };
88
130
  this.on(eventName, wrapper);
89
131
  }
90
- /** Removes a previously registered listener. */
132
+ /**
133
+ * Removes a previously registered listener.
134
+ *
135
+ * @example
136
+ * ```ts
137
+ * emitter.off('build', handler)
138
+ * ```
139
+ */
91
140
  off(eventName, handler) {
92
141
  this.#emitter.off(eventName, handler);
93
142
  }
94
- /** Removes all listeners from every event channel. */
143
+ /**
144
+ * Removes all listeners from every event channel.
145
+ *
146
+ * @example
147
+ * ```ts
148
+ * emitter.removeAll()
149
+ * ```
150
+ */
95
151
  removeAll() {
96
152
  this.#emitter.removeAllListeners();
97
153
  }
98
154
  };
155
+ //#endregion
156
+ //#region ../../internals/utils/src/casing.ts
99
157
  /**
100
158
  * Shared implementation for camelCase and PascalCase conversion.
101
159
  * Splits on common word boundaries (spaces, hyphens, underscores, dots, slashes, colons)
@@ -114,9 +172,12 @@ function toCamelOrPascal(text, pascal) {
114
172
  * Splits `text` on `.` and applies `transformPart` to each segment.
115
173
  * The last segment receives `isLast = true`, all earlier segments receive `false`.
116
174
  * Segments are joined with `/` to form a file path.
175
+ *
176
+ * Only splits on dots followed by a letter so that version numbers
177
+ * embedded in operationIds (e.g. `v2025.0`) are kept intact.
117
178
  */
118
179
  function applyToFileParts(text, transformPart) {
119
- const parts = text.split(".");
180
+ const parts = text.split(/\.(?=[a-zA-Z])/);
120
181
  return parts.map((part, i) => transformPart(part, i === parts.length - 1)).join("/");
121
182
  }
122
183
  /**
@@ -134,190 +195,33 @@ function camelCase(text, { isFile, prefix = "", suffix = "" } = {}) {
134
195
  } : {}));
135
196
  return toCamelOrPascal(`${prefix} ${text} ${suffix}`, false);
136
197
  }
137
- /** Returns a `CLIAdapter` with type inference. Pass a different adapter to `createCLI` to swap the CLI engine. */
138
- function defineCLIAdapter(adapter) {
139
- return adapter;
140
- }
141
198
  /**
142
- * Serializes `CommandDefinition[]` to a plain, JSON-serializable structure.
143
- * Use to expose CLI capabilities to AI agents or MCP tools.
199
+ * Converts `text` to PascalCase.
200
+ * When `isFile` is `true`, the last dot-separated segment is PascalCased and earlier segments are camelCased.
201
+ *
202
+ * @example
203
+ * pascalCase('hello-world') // 'HelloWorld'
204
+ * pascalCase('pet.petId', { isFile: true }) // 'pet/PetId'
144
205
  */
145
- function getCommandSchema(defs) {
146
- return defs.map(serializeCommand);
147
- }
148
- function serializeCommand(def) {
149
- return {
150
- name: def.name,
151
- description: def.description,
152
- arguments: def.arguments,
153
- options: serializeOptions(def.options ?? {}),
154
- subCommands: def.subCommands ? def.subCommands.map(serializeCommand) : []
155
- };
156
- }
157
- function serializeOptions(options) {
158
- return Object.entries(options).map(([name, opt]) => {
159
- return {
160
- name,
161
- flags: `${opt.short ? `-${opt.short}, ` : ""}--${name}${opt.type === "string" ? ` <${opt.hint ?? name}>` : ""}`,
162
- type: opt.type,
163
- description: opt.description,
164
- ...opt.default !== void 0 ? { default: opt.default } : {},
165
- ...opt.hint ? { hint: opt.hint } : {},
166
- ...opt.enum ? { enum: opt.enum } : {},
167
- ...opt.required ? { required: opt.required } : {}
168
- };
169
- });
170
- }
171
- /** Prints formatted help output for a command using its `CommandDefinition`. */
172
- function renderHelp(def, parentName) {
173
- const schema = getCommandSchema([def])[0];
174
- const programName = parentName ? `${parentName} ${schema.name}` : schema.name;
175
- const argsPart = schema.arguments?.length ? ` ${schema.arguments.join(" ")}` : "";
176
- const subCmdPart = schema.subCommands.length ? " <command>" : "";
177
- console.log(`\n${styleText("bold", "Usage:")} ${programName}${argsPart}${subCmdPart} [options]\n`);
178
- if (schema.description) console.log(` ${schema.description}\n`);
179
- if (schema.subCommands.length) {
180
- console.log(styleText("bold", "Commands:"));
181
- for (const sub of schema.subCommands) console.log(` ${styleText("cyan", sub.name.padEnd(16))}${sub.description}`);
182
- console.log();
183
- }
184
- const options = [...schema.options, {
185
- name: "help",
186
- flags: "-h, --help",
187
- type: "boolean",
188
- description: "Show help"
189
- }];
190
- console.log(styleText("bold", "Options:"));
191
- for (const opt of options) {
192
- const flags = styleText("cyan", opt.flags.padEnd(30));
193
- const defaultPart = opt.default !== void 0 ? styleText("dim", ` (default: ${opt.default})`) : "";
194
- console.log(` ${flags}${opt.description}${defaultPart}`);
195
- }
196
- console.log();
197
- }
198
- function buildParseOptions(def) {
199
- const result = { help: {
200
- type: "boolean",
201
- short: "h"
202
- } };
203
- for (const [name, opt] of Object.entries(def.options ?? {})) result[name] = {
204
- type: opt.type,
205
- ...opt.short ? { short: opt.short } : {},
206
- ...opt.default !== void 0 ? { default: opt.default } : {}
207
- };
208
- return result;
206
+ function pascalCase(text, { isFile, prefix = "", suffix = "" } = {}) {
207
+ if (isFile) return applyToFileParts(text, (part, isLast) => isLast ? pascalCase(part, {
208
+ prefix,
209
+ suffix
210
+ }) : camelCase(part));
211
+ return toCamelOrPascal(`${prefix} ${text} ${suffix}`, true);
209
212
  }
210
- async function runCommand(def, argv, parentName) {
211
- const parseOptions = buildParseOptions(def);
212
- let parsed;
213
- try {
214
- const result = parseArgs({
215
- args: argv,
216
- options: parseOptions,
217
- allowPositionals: true,
218
- strict: false
219
- });
220
- parsed = {
221
- values: result.values,
222
- positionals: result.positionals
223
- };
224
- } catch {
225
- renderHelp(def, parentName);
226
- process.exit(1);
227
- }
228
- if (parsed.values["help"]) {
229
- renderHelp(def, parentName);
230
- process.exit(0);
231
- }
232
- for (const [name, opt] of Object.entries(def.options ?? {})) if (opt.required && parsed.values[name] === void 0) {
233
- console.error(styleText("red", `Error: --${name} is required`));
234
- renderHelp(def, parentName);
235
- process.exit(1);
236
- }
237
- if (!def.run) {
238
- renderHelp(def, parentName);
239
- process.exit(0);
240
- }
241
- try {
242
- await def.run(parsed);
243
- } catch (err) {
244
- console.error(styleText("red", `Error: ${err instanceof Error ? err.message : String(err)}`));
245
- renderHelp(def, parentName);
246
- process.exit(1);
247
- }
248
- }
249
- function printRootHelp(programName, version, defs) {
250
- console.log(`\n${styleText("bold", "Usage:")} ${programName} <command> [options]\n`);
251
- console.log(` Kubb generation — v${version}\n`);
252
- console.log(styleText("bold", "Commands:"));
253
- for (const def of defs) console.log(` ${styleText("cyan", def.name.padEnd(16))}${def.description}`);
254
- console.log();
255
- console.log(styleText("bold", "Options:"));
256
- console.log(` ${styleText("cyan", "-v, --version".padEnd(30))}Show version number`);
257
- console.log(` ${styleText("cyan", "-h, --help".padEnd(30))}Show help`);
258
- console.log();
259
- console.log(`Run ${styleText("cyan", `${programName} <command> --help`)} for command-specific help.\n`);
260
- }
261
- defineCLIAdapter({
262
- renderHelp(def, parentName) {
263
- renderHelp(def, parentName);
264
- },
265
- async run(defs, argv, opts) {
266
- const { programName, defaultCommandName, version } = opts;
267
- const args = argv.length >= 2 && argv[0]?.includes("node") ? argv.slice(2) : argv;
268
- if (args[0] === "--version" || args[0] === "-v") {
269
- console.log(version);
270
- process.exit(0);
271
- }
272
- if (args[0] === "--help" || args[0] === "-h") {
273
- printRootHelp(programName, version, defs);
274
- process.exit(0);
275
- }
276
- if (args.length === 0) {
277
- const defaultDef = defs.find((d) => d.name === defaultCommandName);
278
- if (defaultDef?.run) await runCommand(defaultDef, [], programName);
279
- else printRootHelp(programName, version, defs);
280
- return;
281
- }
282
- const [first, ...rest] = args;
283
- const isKnownSubcommand = defs.some((d) => d.name === first);
284
- let def;
285
- let commandArgv;
286
- let parentName;
287
- if (isKnownSubcommand) {
288
- def = defs.find((d) => d.name === first);
289
- commandArgv = rest;
290
- parentName = programName;
291
- } else {
292
- def = defs.find((d) => d.name === defaultCommandName);
293
- commandArgv = args;
294
- parentName = programName;
295
- }
296
- if (!def) {
297
- console.error(`Unknown command: ${first}`);
298
- printRootHelp(programName, version, defs);
299
- process.exit(1);
300
- }
301
- if (def.subCommands?.length) {
302
- const [subName, ...subRest] = commandArgv;
303
- const subDef = def.subCommands.find((s) => s.name === subName);
304
- if (subName === "--help" || subName === "-h") {
305
- renderHelp(def, parentName);
306
- process.exit(0);
307
- }
308
- if (!subDef) {
309
- renderHelp(def, parentName);
310
- process.exit(subName ? 1 : 0);
311
- }
312
- await runCommand(subDef, subRest, `${parentName} ${def.name}`);
313
- return;
314
- }
315
- await runCommand(def, commandArgv, parentName);
316
- }
317
- });
213
+ //#endregion
214
+ //#region ../../internals/utils/src/time.ts
318
215
  /**
319
- * Calculates elapsed time in milliseconds from a high-resolution start time.
320
- * Rounds to 2 decimal places to provide sub-millisecond precision without noise.
216
+ * Calculates elapsed time in milliseconds from a high-resolution `process.hrtime` start time.
217
+ * Rounds to 2 decimal places for sub-millisecond precision without noise.
218
+ *
219
+ * @example
220
+ * ```ts
221
+ * const start = process.hrtime()
222
+ * doWork()
223
+ * getElapsedMs(start) // 42.35
224
+ * ```
321
225
  */
322
226
  function getElapsedMs(hrStart) {
323
227
  const [seconds, nanoseconds] = process.hrtime(hrStart);
@@ -325,39 +229,22 @@ function getElapsedMs(hrStart) {
325
229
  return Math.round(ms * 100) / 100;
326
230
  }
327
231
  /**
328
- * Converts a millisecond duration into a human-readable string.
329
- * Adjusts units (ms, s, m s) based on the magnitude of the duration.
232
+ * Converts a millisecond duration into a human-readable string (`ms`, `s`, or `m s`).
233
+ *
234
+ * @example
235
+ * ```ts
236
+ * formatMs(250) // '250ms'
237
+ * formatMs(1500) // '1.50s'
238
+ * formatMs(90000) // '1m 30.0s'
239
+ * ```
330
240
  */
331
241
  function formatMs(ms) {
332
242
  if (ms >= 6e4) return `${Math.floor(ms / 6e4)}m ${(ms % 6e4 / 1e3).toFixed(1)}s`;
333
243
  if (ms >= 1e3) return `${(ms / 1e3).toFixed(2)}s`;
334
244
  return `${Math.round(ms)}ms`;
335
245
  }
336
- /**
337
- * Parses a CSS hex color string (`#RGB`) into its RGB channels.
338
- * Falls back to `255` for any channel that cannot be parsed.
339
- */
340
- function parseHex(color) {
341
- const int = Number.parseInt(color.replace("#", ""), 16);
342
- return Number.isNaN(int) ? {
343
- r: 255,
344
- g: 255,
345
- b: 255
346
- } : {
347
- r: int >> 16 & 255,
348
- g: int >> 8 & 255,
349
- b: int & 255
350
- };
351
- }
352
- /**
353
- * Returns a function that wraps a string in a 24-bit ANSI true-color escape sequence
354
- * for the given hex color.
355
- */
356
- function hex(color) {
357
- const { r, g, b } = parseHex(color);
358
- return (text) => `\x1b[38;2;${r};${g};${b}m${text}\x1b[0m`;
359
- }
360
- hex("#F55A17"), hex("#F5A217"), hex("#F58517"), hex("#B45309"), hex("#FFFFFF"), hex("#adadc6"), hex("#FDA4AF");
246
+ //#endregion
247
+ //#region ../../internals/utils/src/fs.ts
361
248
  /**
362
249
  * Converts all backslashes to forward slashes.
363
250
  * Extended-length Windows paths (`\\?\...`) are left unchanged.
@@ -367,8 +254,14 @@ function toSlash(p) {
367
254
  return p.replaceAll("\\", "/");
368
255
  }
369
256
  /**
370
- * Returns the relative path from `rootDir` to `filePath`, always using
371
- * forward slashes and prefixed with `./` when not already traversing upward.
257
+ * Returns the relative path from `rootDir` to `filePath`, always using forward slashes
258
+ * and prefixed with `./` when not already traversing upward.
259
+ *
260
+ * @example
261
+ * ```ts
262
+ * getRelativePath('/src/components', '/src/components/Button.tsx') // './Button.tsx'
263
+ * getRelativePath('/src/components', '/src/utils/helpers.ts') // '../utils/helpers.ts'
264
+ * ```
372
265
  */
373
266
  function getRelativePath(rootDir, filePath) {
374
267
  if (!rootDir || !filePath) throw new Error(`Root and file should be filled in when retrieving the relativePath, ${rootDir || ""} ${filePath || ""}`);
@@ -378,43 +271,54 @@ function getRelativePath(rootDir, filePath) {
378
271
  /**
379
272
  * Resolves to `true` when the file or directory at `path` exists.
380
273
  * Uses `Bun.file().exists()` when running under Bun, `fs.access` otherwise.
274
+ *
275
+ * @example
276
+ * ```ts
277
+ * if (await exists('./kubb.config.ts')) {
278
+ * const content = await read('./kubb.config.ts')
279
+ * }
280
+ * ```
381
281
  */
382
282
  async function exists(path) {
383
283
  if (typeof Bun !== "undefined") return Bun.file(path).exists();
384
284
  return access(path).then(() => true, () => false);
385
285
  }
386
286
  /**
387
- * Reads the file at `path` as a UTF-8 string.
388
- * Uses `Bun.file().text()` when running under Bun, `fs.readFile` otherwise.
287
+ * Synchronous counterpart of `read`.
288
+ *
289
+ * @example
290
+ * ```ts
291
+ * const source = readSync('./src/Pet.ts')
292
+ * ```
389
293
  */
390
- async function read(path) {
391
- if (typeof Bun !== "undefined") return Bun.file(path).text();
392
- return readFile(path, { encoding: "utf8" });
393
- }
394
- /** Synchronous counterpart of `read`. */
395
294
  function readSync(path) {
396
295
  return readFileSync(path, { encoding: "utf8" });
397
296
  }
398
297
  /**
399
298
  * Writes `data` to `path`, trimming leading/trailing whitespace before saving.
400
- * Skips the write and returns `undefined` when the trimmed content is empty or
401
- * identical to what is already on disk.
299
+ * Skips the write when the trimmed content is empty or identical to what is already on disk.
402
300
  * Creates any missing parent directories automatically.
403
- * When `sanity` is `true`, re-reads the file after writing and throws if the
404
- * content does not match.
301
+ * When `sanity` is `true`, re-reads the file after writing and throws if the content does not match.
302
+ *
303
+ * @example
304
+ * ```ts
305
+ * await write('./src/Pet.ts', source) // writes and returns trimmed content
306
+ * await write('./src/Pet.ts', source) // null — file unchanged
307
+ * await write('./src/Pet.ts', ' ') // null — empty content skipped
308
+ * ```
405
309
  */
406
310
  async function write(path, data, options = {}) {
407
311
  const trimmed = data.trim();
408
- if (trimmed === "") return void 0;
312
+ if (trimmed === "") return null;
409
313
  const resolved = resolve(path);
410
314
  if (typeof Bun !== "undefined") {
411
315
  const file = Bun.file(resolved);
412
- if ((await file.exists() ? await file.text() : null) === trimmed) return void 0;
316
+ if ((await file.exists() ? await file.text() : null) === trimmed) return null;
413
317
  await Bun.write(resolved, trimmed);
414
318
  return trimmed;
415
319
  }
416
320
  try {
417
- if (await readFile(resolved, { encoding: "utf-8" }) === trimmed) return void 0;
321
+ if (await readFile(resolved, { encoding: "utf-8" }) === trimmed) return null;
418
322
  } catch {}
419
323
  await mkdir(dirname(resolved), { recursive: true });
420
324
  await writeFile(resolved, trimmed, { encoding: "utf-8" });
@@ -425,16 +329,32 @@ async function write(path, data, options = {}) {
425
329
  }
426
330
  return trimmed;
427
331
  }
428
- /** Recursively removes `path`. Silently succeeds when `path` does not exist. */
332
+ /**
333
+ * Recursively removes `path`. Silently succeeds when `path` does not exist.
334
+ *
335
+ * @example
336
+ * ```ts
337
+ * await clean('./dist')
338
+ * ```
339
+ */
429
340
  async function clean(path) {
430
341
  return rm(path, {
431
342
  recursive: true,
432
343
  force: true
433
344
  });
434
345
  }
346
+ //#endregion
347
+ //#region ../../internals/utils/src/names.ts
435
348
  /**
436
349
  * Registers `originalName` in `data` without altering the returned name.
437
- * Use this when you need to track usage frequency but always emit the original identifier.
350
+ * Use when you need to track usage frequency but always emit the original identifier.
351
+ *
352
+ * @example
353
+ * ```ts
354
+ * const seen: Record<string, number> = {}
355
+ * setUniqueName('Foo', seen) // 'Foo' (seen = { Foo: 1 })
356
+ * setUniqueName('Foo', seen) // 'Foo' (seen = { Foo: 2 })
357
+ * ```
438
358
  */
439
359
  function setUniqueName(originalName, data) {
440
360
  let used = data[originalName] || 0;
@@ -445,11 +365,26 @@ function setUniqueName(originalName, data) {
445
365
  data[originalName] = 1;
446
366
  return originalName;
447
367
  }
368
+ //#endregion
369
+ //#region ../../internals/utils/src/promise.ts
370
+ /** Returns `true` when `result` is a rejected `Promise.allSettled` result with a typed `reason`.
371
+ *
372
+ * @example
373
+ * ```ts
374
+ * const results = await Promise.allSettled([p1, p2])
375
+ * results.filter(isPromiseRejectedResult<Error>).map((r) => r.reason.message)
376
+ * ```
377
+ */
378
+ function isPromiseRejectedResult(result) {
379
+ return result.status === "rejected";
380
+ }
381
+ //#endregion
382
+ //#region ../../internals/utils/src/reserved.ts
448
383
  /**
449
384
  * JavaScript and Java reserved words.
450
385
  * @link https://github.com/jonschlinkert/reserved/blob/master/index.js
451
386
  */
452
- const reservedWords = [
387
+ const reservedWords = new Set([
453
388
  "abstract",
454
389
  "arguments",
455
390
  "boolean",
@@ -531,18 +466,31 @@ const reservedWords = [
531
466
  "toString",
532
467
  "undefined",
533
468
  "valueOf"
534
- ];
469
+ ]);
535
470
  /**
536
- * Prefixes a word with `_` when it is a reserved JavaScript/Java identifier
537
- * or starts with a digit.
471
+ * Prefixes `word` with `_` when it is a reserved JavaScript/Java identifier or starts with a digit.
472
+ *
473
+ * @example
474
+ * ```ts
475
+ * transformReservedWord('class') // '_class'
476
+ * transformReservedWord('42foo') // '_42foo'
477
+ * transformReservedWord('status') // 'status'
478
+ * ```
538
479
  */
539
480
  function transformReservedWord(word) {
540
481
  const firstChar = word.charCodeAt(0);
541
- if (word && (reservedWords.includes(word) || firstChar >= 48 && firstChar <= 57)) return `_${word}`;
482
+ if (word && (reservedWords.has(word) || firstChar >= 48 && firstChar <= 57)) return `_${word}`;
542
483
  return word;
543
484
  }
544
485
  /**
545
486
  * Returns `true` when `name` is a syntactically valid JavaScript variable name.
487
+ *
488
+ * @example
489
+ * ```ts
490
+ * isValidVarName('status') // true
491
+ * isValidVarName('class') // false (reserved word)
492
+ * isValidVarName('42foo') // false (starts with digit)
493
+ * ```
546
494
  */
547
495
  function isValidVarName(name) {
548
496
  try {
@@ -552,6 +500,8 @@ function isValidVarName(name) {
552
500
  }
553
501
  return true;
554
502
  }
503
+ //#endregion
504
+ //#region ../../internals/utils/src/urlPath.ts
555
505
  /**
556
506
  * Parses and transforms an OpenAPI/Swagger path string into various URL formats.
557
507
  *
@@ -561,18 +511,33 @@ function isValidVarName(name) {
561
511
  * p.template // '`/pet/${petId}`'
562
512
  */
563
513
  var URLPath = class {
564
- /** The raw OpenAPI/Swagger path string, e.g. `/pet/{petId}`. */
514
+ /**
515
+ * The raw OpenAPI/Swagger path string, e.g. `/pet/{petId}`.
516
+ */
565
517
  path;
566
518
  #options;
567
519
  constructor(path, options = {}) {
568
520
  this.path = path;
569
521
  this.#options = options;
570
522
  }
571
- /** Converts the OpenAPI path to Express-style colon syntax, e.g. `/pet/{petId}` → `/pet/:petId`. */
523
+ /** Converts the OpenAPI path to Express-style colon syntax, e.g. `/pet/{petId}` → `/pet/:petId`.
524
+ *
525
+ * @example
526
+ * ```ts
527
+ * new URLPath('/pet/{petId}').URL // '/pet/:petId'
528
+ * ```
529
+ */
572
530
  get URL() {
573
531
  return this.toURLPath();
574
532
  }
575
- /** Returns `true` when `path` is a fully-qualified URL (e.g. starts with `https://`). */
533
+ /** Returns `true` when `path` is a fully-qualified URL (e.g. starts with `https://`).
534
+ *
535
+ * @example
536
+ * ```ts
537
+ * new URLPath('https://petstore.swagger.io/v2/pet').isURL // true
538
+ * new URLPath('/pet/{petId}').isURL // false
539
+ * ```
540
+ */
576
541
  get isURL() {
577
542
  try {
578
543
  return !!new URL(this.path).href;
@@ -590,11 +555,25 @@ var URLPath = class {
590
555
  get template() {
591
556
  return this.toTemplateString();
592
557
  }
593
- /** Returns the path and its extracted params as a structured `URLObject`, or as a stringified expression when `stringify` is set. */
558
+ /** Returns the path and its extracted params as a structured `URLObject`, or as a stringified expression when `stringify` is set.
559
+ *
560
+ * @example
561
+ * ```ts
562
+ * new URLPath('/pet/{petId}').object
563
+ * // { url: '/pet/:petId', params: { petId: 'petId' } }
564
+ * ```
565
+ */
594
566
  get object() {
595
567
  return this.toObject();
596
568
  }
597
- /** Returns a map of path parameter names, or `undefined` when the path has no parameters. */
569
+ /** Returns a map of path parameter names, or `undefined` when the path has no parameters.
570
+ *
571
+ * @example
572
+ * ```ts
573
+ * new URLPath('/pet/{petId}').params // { petId: 'petId' }
574
+ * new URLPath('/pet').params // undefined
575
+ * ```
576
+ */
598
577
  get params() {
599
578
  return this.getParams();
600
579
  }
@@ -602,7 +581,9 @@ var URLPath = class {
602
581
  const param = isValidVarName(raw) ? raw : camelCase(raw);
603
582
  return this.#options.casing === "camelcase" ? camelCase(param) : param;
604
583
  }
605
- /** Iterates over every `{param}` token in `path`, calling `fn` with the raw token and transformed name. */
584
+ /**
585
+ * Iterates over every `{param}` token in `path`, calling `fn` with the raw token and transformed name.
586
+ */
606
587
  #eachParam(fn) {
607
588
  for (const match of this.path.matchAll(/\{([^}]+)\}/g)) {
608
589
  const raw = match[1];
@@ -639,6 +620,12 @@ var URLPath = class {
639
620
  * Extracts all `{param}` segments from the path and returns them as a key-value map.
640
621
  * An optional `replacer` transforms each parameter name in both key and value positions.
641
622
  * Returns `undefined` when no path parameters are found.
623
+ *
624
+ * @example
625
+ * ```ts
626
+ * new URLPath('/pet/{petId}/tag/{tagId}').getParams()
627
+ * // { petId: 'petId', tagId: 'tagId' }
628
+ * ```
642
629
  */
643
630
  getParams(replacer) {
644
631
  const params = {};
@@ -648,7 +635,13 @@ var URLPath = class {
648
635
  });
649
636
  return Object.keys(params).length > 0 ? params : void 0;
650
637
  }
651
- /** Converts the OpenAPI path to Express-style colon syntax, e.g. `/pet/{petId}` → `/pet/:petId`. */
638
+ /** Converts the OpenAPI path to Express-style colon syntax.
639
+ *
640
+ * @example
641
+ * ```ts
642
+ * new URLPath('/pet/{petId}').toURLPath() // '/pet/:petId'
643
+ * ```
644
+ */
652
645
  toURLPath() {
653
646
  return this.path.replace(/\{([^}]+)\}/g, ":$1");
654
647
  }
@@ -666,11 +659,27 @@ function isInputPath(config) {
666
659
  }
667
660
  //#endregion
668
661
  //#region src/constants.ts
662
+ /**
663
+ * Base URL for the Kubb Studio web app.
664
+ */
669
665
  const DEFAULT_STUDIO_URL = "https://studio.kubb.dev";
666
+ /**
667
+ * File name used for generated barrel (index) files.
668
+ */
670
669
  const BARREL_FILENAME = "index.ts";
670
+ /**
671
+ * Default banner style written at the top of every generated file.
672
+ */
671
673
  const DEFAULT_BANNER = "simple";
674
+ /**
675
+ * Default file-extension mapping used when no explicit mapping is configured.
676
+ */
672
677
  const DEFAULT_EXTENSION = { ".ts": ".ts" };
673
- const PATH_SEPARATORS = ["/", "\\"];
678
+ /**
679
+ * Numeric log-level thresholds used internally to compare verbosity.
680
+ *
681
+ * Higher numbers are more verbose.
682
+ */
674
683
  const logLevel = {
675
684
  silent: Number.NEGATIVE_INFINITY,
676
685
  error: 0,
@@ -679,6 +688,13 @@ const logLevel = {
679
688
  verbose: 4,
680
689
  debug: 5
681
690
  };
691
+ /**
692
+ * CLI command descriptors for each supported linter.
693
+ *
694
+ * Each entry contains the executable `command`, an `args` factory that maps an
695
+ * output path to the correct argument list, and an `errorMessage` shown when
696
+ * the linter is not found.
697
+ */
682
698
  const linters = {
683
699
  eslint: {
684
700
  command: "eslint",
@@ -700,6 +716,13 @@ const linters = {
700
716
  errorMessage: "Oxlint not found"
701
717
  }
702
718
  };
719
+ /**
720
+ * CLI command descriptors for each supported code formatter.
721
+ *
722
+ * Each entry contains the executable `command`, an `args` factory that maps an
723
+ * output path to the correct argument list, and an `errorMessage` shown when
724
+ * the formatter is not found.
725
+ */
703
726
  const formatters = {
704
727
  prettier: {
705
728
  command: "prettier",
@@ -899,7 +922,11 @@ function validateConcurrency(concurrency) {
899
922
  //#endregion
900
923
  //#region src/utils/executeStrategies.ts
901
924
  /**
902
- * Chains promises
925
+ * Runs promise functions in sequence, threading each result into the next call.
926
+ *
927
+ * - Each function receives the accumulated state from the previous call.
928
+ * - Skips functions that return a falsy value (acts as a no-op for that step).
929
+ * - Returns an array of all individual results.
903
930
  */
904
931
  function hookSeq(promises) {
905
932
  return promises.filter(Boolean).reduce((promise, func) => {
@@ -912,7 +939,10 @@ function hookSeq(promises) {
912
939
  }, Promise.resolve([]));
913
940
  }
914
941
  /**
915
- * Chains promises, first non-null result stops and returns
942
+ * Runs promise functions in sequence and returns the first non-null result.
943
+ *
944
+ * - Stops as soon as `nullCheck` passes for a result (default: `!== null`).
945
+ * - Subsequent functions are skipped once a match is found.
916
946
  */
917
947
  function hookFirst(promises, nullCheck = (state) => state !== null) {
918
948
  let promise = Promise.resolve(null);
@@ -923,7 +953,10 @@ function hookFirst(promises, nullCheck = (state) => state !== null) {
923
953
  return promise;
924
954
  }
925
955
  /**
926
- * Runs an array of promise functions with optional concurrency limit.
956
+ * Runs promise functions concurrently and returns all settled results.
957
+ *
958
+ * - Limits simultaneous executions to `concurrency` (default: unlimited).
959
+ * - Uses `Promise.allSettled` so individual failures do not cancel other tasks.
927
960
  */
928
961
  function hookParallel(promises, concurrency = Number.POSITIVE_INFINITY) {
929
962
  const limit = pLimit(concurrency);
@@ -931,29 +964,13 @@ function hookParallel(promises, concurrency = Number.POSITIVE_INFINITY) {
931
964
  return Promise.allSettled(tasks);
932
965
  }
933
966
  //#endregion
934
- //#region src/PromiseManager.ts
935
- var PromiseManager = class {
936
- #options = {};
937
- constructor(options = {}) {
938
- this.#options = options;
939
- }
940
- run(strategy, promises, { concurrency = Number.POSITIVE_INFINITY } = {}) {
941
- if (strategy === "seq") return hookSeq(promises);
942
- if (strategy === "first") return hookFirst(promises, this.#options.nullCheck);
943
- if (strategy === "parallel") return hookParallel(promises, concurrency);
944
- throw new Error(`${strategy} not implemented`);
945
- }
946
- };
947
- function isPromiseRejectedResult(result) {
948
- return result.status === "rejected";
949
- }
950
- //#endregion
951
- //#region src/PluginManager.ts
967
+ //#region src/PluginDriver.ts
952
968
  function getMode(fileOrFolder) {
953
969
  if (!fileOrFolder) return "split";
954
- return path.extname(fileOrFolder) ? "single" : "split";
970
+ return extname(fileOrFolder) ? "single" : "split";
955
971
  }
956
- var PluginManager = class {
972
+ const hookFirstNullCheck = (state) => !!state?.result;
973
+ var PluginDriver = class {
957
974
  config;
958
975
  options;
959
976
  /**
@@ -961,14 +978,13 @@ var PluginManager = class {
961
978
  * the build pipeline after the adapter's `parse()` resolves.
962
979
  */
963
980
  rootNode = void 0;
981
+ adapter = void 0;
964
982
  #studioIsOpen = false;
965
983
  #plugins = /* @__PURE__ */ new Set();
966
984
  #usedPluginNames = {};
967
- #promiseManager;
968
985
  constructor(config, options) {
969
986
  this.config = config;
970
987
  this.options = options;
971
- this.#promiseManager = new PromiseManager({ nullCheck: (state) => !!state?.result });
972
988
  [...config.plugins || []].forEach((plugin) => {
973
989
  const parsedPlugin = this.#parse(plugin);
974
990
  this.#plugins.add(parsedPlugin);
@@ -979,14 +995,14 @@ var PluginManager = class {
979
995
  }
980
996
  getContext(plugin) {
981
997
  const plugins = [...this.#plugins];
982
- const pluginManager = this;
998
+ const driver = this;
983
999
  const baseContext = {
984
1000
  fabric: this.options.fabric,
985
1001
  config: this.config,
986
1002
  plugin,
987
1003
  events: this.options.events,
988
- pluginManager: this,
989
- mode: getMode(path.resolve(this.config.root, this.config.output.path)),
1004
+ driver: this,
1005
+ mode: getMode(resolve(this.config.root, this.config.output.path)),
990
1006
  addFile: async (...files) => {
991
1007
  await this.options.fabric.addFile(...files);
992
1008
  },
@@ -994,15 +1010,18 @@ var PluginManager = class {
994
1010
  await this.options.fabric.upsertFile(...files);
995
1011
  },
996
1012
  get rootNode() {
997
- return pluginManager.rootNode;
1013
+ return driver.rootNode;
1014
+ },
1015
+ get adapter() {
1016
+ return driver.adapter;
998
1017
  },
999
1018
  openInStudio(options) {
1000
- if (typeof pluginManager.config.devtools !== "object") throw new Error("Devtools must be an object");
1001
- if (!pluginManager.rootNode) throw new Error("RootNode is not defined, make sure you have set the parser in kubb.config.ts");
1002
- if (pluginManager.#studioIsOpen) return;
1003
- pluginManager.#studioIsOpen = true;
1004
- const studioUrl = pluginManager.config.devtools?.studioUrl ?? "https://studio.kubb.dev";
1005
- return openInStudio(pluginManager.rootNode, studioUrl, options);
1019
+ if (!driver.config.devtools || driver.#studioIsOpen) return;
1020
+ if (typeof driver.config.devtools !== "object") throw new Error("Devtools must be an object");
1021
+ if (!driver.rootNode || !driver.adapter) throw new Error("adapter is not defined, make sure you have set the parser in kubb.config.ts");
1022
+ driver.#studioIsOpen = true;
1023
+ const studioUrl = driver.config.devtools?.studioUrl ?? "https://studio.kubb.dev";
1024
+ return openInStudio(driver.rootNode, studioUrl, options);
1006
1025
  }
1007
1026
  };
1008
1027
  const mergedExtras = {};
@@ -1019,17 +1038,21 @@ var PluginManager = class {
1019
1038
  return this.#getSortedPlugins();
1020
1039
  }
1021
1040
  getFile({ name, mode, extname, pluginName, options }) {
1022
- const baseName = `${name}${extname}`;
1041
+ const resolvedName = mode ? mode === "single" ? "" : this.resolveName({
1042
+ name,
1043
+ pluginName,
1044
+ type: "file"
1045
+ }) : name;
1023
1046
  const path = this.resolvePath({
1024
- baseName,
1047
+ baseName: `${resolvedName}${extname}`,
1025
1048
  mode,
1026
1049
  pluginName,
1027
1050
  options
1028
1051
  });
1029
- if (!path) throw new Error(`Filepath should be defined for resolvedName "${name}" and pluginName "${pluginName}"`);
1052
+ if (!path) throw new Error(`Filepath should be defined for resolvedName "${resolvedName}" and pluginName "${pluginName}"`);
1030
1053
  return {
1031
1054
  path,
1032
- baseName,
1055
+ baseName: basename(path),
1033
1056
  meta: { pluginName },
1034
1057
  sources: [],
1035
1058
  imports: [],
@@ -1037,8 +1060,7 @@ var PluginManager = class {
1037
1060
  };
1038
1061
  }
1039
1062
  resolvePath = (params) => {
1040
- const root = path.resolve(this.config.root, this.config.output.path);
1041
- const defaultPath = path.resolve(root, params.baseName);
1063
+ const defaultPath = resolve(resolve(this.config.root, this.config.output.path), params.baseName);
1042
1064
  if (params.pluginName) return this.hookForPluginSync({
1043
1065
  pluginName: params.pluginName,
1044
1066
  hookName: "resolvePath",
@@ -1118,7 +1140,7 @@ var PluginManager = class {
1118
1140
  hookName,
1119
1141
  plugins
1120
1142
  });
1121
- const promises = plugins.map((plugin) => {
1143
+ const result = await hookFirst(plugins.map((plugin) => {
1122
1144
  return async () => {
1123
1145
  const value = await this.#execute({
1124
1146
  strategy: "hookFirst",
@@ -1131,8 +1153,7 @@ var PluginManager = class {
1131
1153
  result: value
1132
1154
  });
1133
1155
  };
1134
- });
1135
- const result = await this.#promiseManager.run("first", promises);
1156
+ }), hookFirstNullCheck);
1136
1157
  this.events.emit("plugins:hook:progress:end", { hookName });
1137
1158
  return result;
1138
1159
  }
@@ -1168,7 +1189,7 @@ var PluginManager = class {
1168
1189
  plugins
1169
1190
  });
1170
1191
  const pluginStartTimes = /* @__PURE__ */ new Map();
1171
- const promises = plugins.map((plugin) => {
1192
+ const results = await hookParallel(plugins.map((plugin) => {
1172
1193
  return () => {
1173
1194
  pluginStartTimes.set(plugin, performance.now());
1174
1195
  return this.#execute({
@@ -1178,8 +1199,7 @@ var PluginManager = class {
1178
1199
  plugin
1179
1200
  });
1180
1201
  };
1181
- });
1182
- const results = await this.#promiseManager.run("parallel", promises, { concurrency: this.options.concurrency });
1202
+ }), this.options.concurrency);
1183
1203
  results.forEach((result, index) => {
1184
1204
  if (isPromiseRejectedResult(result)) {
1185
1205
  const plugin = this.#getSortedPlugins(hookName)[index];
@@ -1210,15 +1230,14 @@ var PluginManager = class {
1210
1230
  hookName,
1211
1231
  plugins
1212
1232
  });
1213
- const promises = plugins.map((plugin) => {
1233
+ await hookSeq(plugins.map((plugin) => {
1214
1234
  return () => this.#execute({
1215
1235
  strategy: "hookSeq",
1216
1236
  hookName,
1217
1237
  parameters,
1218
1238
  plugin
1219
1239
  });
1220
- });
1221
- await this.#promiseManager.run("seq", promises);
1240
+ }));
1222
1241
  this.events.emit("plugins:hook:progress:end", { hookName });
1223
1242
  }
1224
1243
  #getSortedPlugins(hookName) {
@@ -1226,7 +1245,8 @@ var PluginManager = class {
1226
1245
  if (hookName) return plugins.filter((plugin) => hookName in plugin);
1227
1246
  return plugins.map((plugin) => {
1228
1247
  if (plugin.pre) {
1229
- const missingPlugins = plugin.pre.filter((pluginName) => !plugins.find((pluginToFind) => pluginToFind.name === pluginName));
1248
+ let missingPlugins = plugin.pre.filter((pluginName) => !plugins.find((pluginToFind) => pluginToFind.name === pluginName));
1249
+ if (missingPlugins.includes("plugin-oas") && this.adapter) missingPlugins = missingPlugins.filter((pluginName) => pluginName !== "plugin-oas");
1230
1250
  if (missingPlugins.length > 0) throw new ValidationPluginError(`The plugin '${plugin.name}' has a pre set that references missing plugins for '${missingPlugins.join(", ")}'`);
1231
1251
  }
1232
1252
  return plugin;
@@ -1346,19 +1366,12 @@ var PluginManager = class {
1346
1366
  }
1347
1367
  };
1348
1368
  //#endregion
1349
- //#region src/defineStorage.ts
1369
+ //#region src/createStorage.ts
1350
1370
  /**
1351
- * Wraps a storage builder so the `options` argument is optional, following the
1352
- * same factory pattern as `definePlugin`, `defineLogger`, and `defineAdapter`.
1353
- *
1354
- * The builder receives the resolved options object and must return a
1355
- * `DefineStorage`-compatible object that includes a `name` string.
1371
+ * Creates a storage factory. Call the returned function with optional options to get the storage instance.
1356
1372
  *
1357
1373
  * @example
1358
- * ```ts
1359
- * import { defineStorage } from '@kubb/core'
1360
- *
1361
- * export const memoryStorage = defineStorage((_options) => {
1374
+ * export const memoryStorage = createStorage(() => {
1362
1375
  * const store = new Map<string, string>()
1363
1376
  * return {
1364
1377
  * name: 'memory',
@@ -1366,13 +1379,15 @@ var PluginManager = class {
1366
1379
  * async getItem(key) { return store.get(key) ?? null },
1367
1380
  * async setItem(key, value) { store.set(key, value) },
1368
1381
  * async removeItem(key) { store.delete(key) },
1369
- * async getKeys() { return [...store.keys()] },
1370
- * async clear() { store.clear() },
1382
+ * async getKeys(base) {
1383
+ * const keys = [...store.keys()]
1384
+ * return base ? keys.filter((k) => k.startsWith(base)) : keys
1385
+ * },
1386
+ * async clear(base) { if (!base) store.clear() },
1371
1387
  * }
1372
1388
  * })
1373
- * ```
1374
1389
  */
1375
- function defineStorage(build) {
1390
+ function createStorage(build) {
1376
1391
  return (options) => build(options ?? {});
1377
1392
  }
1378
1393
  //#endregion
@@ -1400,7 +1415,7 @@ function defineStorage(build) {
1400
1415
  * })
1401
1416
  * ```
1402
1417
  */
1403
- const fsStorage = defineStorage(() => ({
1418
+ const fsStorage = createStorage(() => ({
1404
1419
  name: "fs",
1405
1420
  async hasItem(key) {
1406
1421
  try {
@@ -1448,11 +1463,14 @@ const fsStorage = defineStorage(() => ({
1448
1463
  }));
1449
1464
  //#endregion
1450
1465
  //#region package.json
1451
- var version$1 = "5.0.0-alpha.2";
1466
+ var version$1 = "5.0.0-alpha.20";
1452
1467
  //#endregion
1453
1468
  //#region src/utils/diagnostics.ts
1454
1469
  /**
1455
- * Get diagnostic information for debugging
1470
+ * Returns a snapshot of the current runtime environment.
1471
+ *
1472
+ * Useful for attaching context to debug logs and error reports so that
1473
+ * issues can be reproduced without manual information gathering.
1456
1474
  */
1457
1475
  function getDiagnosticInfo() {
1458
1476
  return {
@@ -1465,6 +1483,17 @@ function getDiagnosticInfo() {
1465
1483
  }
1466
1484
  //#endregion
1467
1485
  //#region src/build.ts
1486
+ /**
1487
+ * Initializes all Kubb infrastructure for a build without executing any plugins.
1488
+ *
1489
+ * - Validates the input path (when applicable).
1490
+ * - Applies config defaults (`root`, `output.*`, `devtools`).
1491
+ * - Creates the Fabric instance and wires storage, format, and lint hooks.
1492
+ * - Runs the adapter (if configured) to produce the universal `RootNode`.
1493
+ *
1494
+ * Pass the returned {@link SetupResult} directly to {@link safeBuild} or {@link build}
1495
+ * via the `overrides` argument to reuse the same infrastructure across multiple runs.
1496
+ */
1468
1497
  async function setup(options) {
1469
1498
  const { config: userConfig, events = new AsyncEventEmitter() } = options;
1470
1499
  const sources = /* @__PURE__ */ new Map();
@@ -1562,7 +1591,7 @@ async function setup(options) {
1562
1591
  ` • Barrel type: ${definedConfig.output.barrelType || "none"}`
1563
1592
  ]
1564
1593
  });
1565
- const pluginManager = new PluginManager(definedConfig, {
1594
+ const pluginDriver = new PluginDriver(definedConfig, {
1566
1595
  fabric,
1567
1596
  events,
1568
1597
  concurrency: 15
@@ -1573,25 +1602,32 @@ async function setup(options) {
1573
1602
  date: /* @__PURE__ */ new Date(),
1574
1603
  logs: [`Running adapter: ${definedConfig.adapter.name}`]
1575
1604
  });
1576
- pluginManager.rootNode = await definedConfig.adapter.parse(source);
1605
+ pluginDriver.adapter = definedConfig.adapter;
1606
+ pluginDriver.rootNode = await definedConfig.adapter.parse(source);
1577
1607
  await events.emit("debug", {
1578
1608
  date: /* @__PURE__ */ new Date(),
1579
1609
  logs: [
1580
1610
  `✓ Adapter '${definedConfig.adapter.name}' resolved RootNode`,
1581
- ` • Schemas: ${pluginManager.rootNode.schemas.length}`,
1582
- ` • Operations: ${pluginManager.rootNode.operations.length}`
1611
+ ` • Schemas: ${pluginDriver.rootNode.schemas.length}`,
1612
+ ` • Operations: ${pluginDriver.rootNode.operations.length}`
1583
1613
  ]
1584
1614
  });
1585
1615
  }
1586
1616
  return {
1587
1617
  events,
1588
1618
  fabric,
1589
- pluginManager,
1619
+ driver: pluginDriver,
1590
1620
  sources
1591
1621
  };
1592
1622
  }
1623
+ /**
1624
+ * Runs a full Kubb build and throws on any error or plugin failure.
1625
+ *
1626
+ * Internally delegates to {@link safeBuild} and rethrows collected errors.
1627
+ * Pass an existing {@link SetupResult} via `overrides` to skip the setup phase.
1628
+ */
1593
1629
  async function build(options, overrides) {
1594
- const { fabric, files, pluginManager, failedPlugins, pluginTimings, error, sources } = await safeBuild(options, overrides);
1630
+ const { fabric, files, driver, failedPlugins, pluginTimings, error, sources } = await safeBuild(options, overrides);
1595
1631
  if (error) throw error;
1596
1632
  if (failedPlugins.size > 0) {
1597
1633
  const errors = [...failedPlugins].map(({ error }) => error);
@@ -1601,20 +1637,30 @@ async function build(options, overrides) {
1601
1637
  failedPlugins,
1602
1638
  fabric,
1603
1639
  files,
1604
- pluginManager,
1640
+ driver,
1605
1641
  pluginTimings,
1606
1642
  error: void 0,
1607
1643
  sources
1608
1644
  };
1609
1645
  }
1646
+ /**
1647
+ * Runs a full Kubb build and captures errors instead of throwing.
1648
+ *
1649
+ * - Installs each plugin in order, recording failures in `failedPlugins`.
1650
+ * - Generates the root barrel file when `output.barrelType` is set.
1651
+ * - Writes all files through Fabric.
1652
+ *
1653
+ * Returns a {@link BuildOutput} even on failure — inspect `error` and
1654
+ * `failedPlugins` to determine whether the build succeeded.
1655
+ */
1610
1656
  async function safeBuild(options, overrides) {
1611
- const { fabric, pluginManager, events, sources } = overrides ? overrides : await setup(options);
1657
+ const { fabric, driver, events, sources } = overrides ? overrides : await setup(options);
1612
1658
  const failedPlugins = /* @__PURE__ */ new Set();
1613
1659
  const pluginTimings = /* @__PURE__ */ new Map();
1614
- const config = pluginManager.config;
1660
+ const config = driver.config;
1615
1661
  try {
1616
- for (const plugin of pluginManager.plugins) {
1617
- const context = pluginManager.getContext(plugin);
1662
+ for (const plugin of driver.plugins) {
1663
+ const context = driver.getContext(plugin);
1618
1664
  const hrStart = process.hrtime();
1619
1665
  const installer = plugin.install.bind(context);
1620
1666
  try {
@@ -1687,7 +1733,7 @@ async function safeBuild(options, overrides) {
1687
1733
  rootDir,
1688
1734
  existingExports: new Set(existingBarrel?.exports?.flatMap((e) => Array.isArray(e.name) ? e.name : [e.name]).filter((n) => Boolean(n)) ?? []),
1689
1735
  config,
1690
- pluginManager
1736
+ driver
1691
1737
  }),
1692
1738
  sources: [],
1693
1739
  imports: [],
@@ -1705,7 +1751,7 @@ async function safeBuild(options, overrides) {
1705
1751
  failedPlugins,
1706
1752
  fabric,
1707
1753
  files,
1708
- pluginManager,
1754
+ driver,
1709
1755
  pluginTimings,
1710
1756
  sources
1711
1757
  };
@@ -1714,16 +1760,16 @@ async function safeBuild(options, overrides) {
1714
1760
  failedPlugins,
1715
1761
  fabric,
1716
1762
  files: [],
1717
- pluginManager,
1763
+ driver,
1718
1764
  pluginTimings,
1719
1765
  error,
1720
1766
  sources
1721
1767
  };
1722
1768
  }
1723
1769
  }
1724
- function buildBarrelExports({ barrelFiles, rootDir, existingExports, config, pluginManager }) {
1770
+ function buildBarrelExports({ barrelFiles, rootDir, existingExports, config, driver }) {
1725
1771
  const pluginNameMap = /* @__PURE__ */ new Map();
1726
- for (const plugin of pluginManager.plugins) pluginNameMap.set(plugin.name, plugin);
1772
+ for (const plugin of driver.plugins) pluginNameMap.set(plugin.name, plugin);
1727
1773
  return barrelFiles.flatMap((file) => {
1728
1774
  const containsOnlyTypes = file.sources?.every((source) => source.isTypeOnly);
1729
1775
  return (file.sources ?? []).flatMap((source) => {
@@ -1748,133 +1794,301 @@ function buildBarrelExports({ barrelFiles, rootDir, existingExports, config, plu
1748
1794
  function inputToAdapterSource(config) {
1749
1795
  if (Array.isArray(config.input)) return {
1750
1796
  type: "paths",
1751
- paths: config.input.map((i) => resolve(config.root, i.path))
1797
+ paths: config.input.map((i) => new URLPath(i.path).isURL ? i.path : resolve(config.root, i.path))
1752
1798
  };
1753
1799
  if ("data" in config.input) return {
1754
1800
  type: "data",
1755
1801
  data: config.input.data
1756
1802
  };
1803
+ if (new URLPath(config.input.path).isURL) return {
1804
+ type: "path",
1805
+ path: config.input.path
1806
+ };
1757
1807
  return {
1758
1808
  type: "path",
1759
1809
  path: resolve(config.root, config.input.path)
1760
1810
  };
1761
1811
  }
1762
1812
  //#endregion
1763
- //#region src/defineAdapter.ts
1813
+ //#region src/createAdapter.ts
1764
1814
  /**
1765
- * Wraps an adapter builder to make the options parameter optional.
1815
+ * Creates an adapter factory. Call the returned function with optional options to get the adapter instance.
1766
1816
  *
1767
1817
  * @example
1768
- * ```ts
1769
- * export const adapterOas = defineAdapter<OasAdapter>((options) => {
1770
- * const { validate = true, dateType = 'string' } = options
1818
+ * export const myAdapter = createAdapter<MyAdapter>((options) => {
1771
1819
  * return {
1772
- * name: adapterOasName,
1773
- * options: { validate, dateType, ... },
1774
- * parse(source) { ... },
1820
+ * name: 'my-adapter',
1821
+ * options,
1822
+ * async parse(source) { ... },
1775
1823
  * }
1776
1824
  * })
1777
- * ```
1825
+ *
1826
+ * // instantiate
1827
+ * const adapter = myAdapter({ validate: true })
1828
+ */
1829
+ function createAdapter(build) {
1830
+ return (options) => build(options ?? {});
1831
+ }
1832
+ //#endregion
1833
+ //#region src/createPlugin.ts
1834
+ /**
1835
+ * Creates a plugin factory. Call the returned function with optional options to get the plugin instance.
1836
+ *
1837
+ * @example
1838
+ * export const myPlugin = createPlugin<MyPlugin>((options) => {
1839
+ * return {
1840
+ * name: 'my-plugin',
1841
+ * options,
1842
+ * resolvePath(baseName) { ... },
1843
+ * resolveName(name, type) { ... },
1844
+ * }
1845
+ * })
1846
+ *
1847
+ * // instantiate
1848
+ * const plugin = myPlugin({ output: { path: 'src/gen' } })
1778
1849
  */
1779
- function defineAdapter(build) {
1850
+ function createPlugin(build) {
1780
1851
  return (options) => build(options ?? {});
1781
1852
  }
1782
1853
  //#endregion
1854
+ //#region src/defineGenerator.ts
1855
+ function defineGenerator(generator) {
1856
+ if (generator.type === "react") return {
1857
+ version: "2",
1858
+ Operations() {
1859
+ return null;
1860
+ },
1861
+ Operation() {
1862
+ return null;
1863
+ },
1864
+ Schema() {
1865
+ return null;
1866
+ },
1867
+ ...generator
1868
+ };
1869
+ return {
1870
+ version: "2",
1871
+ async operations() {
1872
+ return [];
1873
+ },
1874
+ async operation() {
1875
+ return [];
1876
+ },
1877
+ async schema() {
1878
+ return [];
1879
+ },
1880
+ ...generator
1881
+ };
1882
+ }
1883
+ //#endregion
1783
1884
  //#region src/defineLogger.ts
1885
+ /**
1886
+ * Wraps a logger definition into a typed {@link Logger}.
1887
+ *
1888
+ * @example
1889
+ * export const myLogger = defineLogger({
1890
+ * name: 'my-logger',
1891
+ * install(context, options) {
1892
+ * context.on('info', (message) => console.log('ℹ', message))
1893
+ * context.on('error', (error) => console.error('✗', error.message))
1894
+ * },
1895
+ * })
1896
+ */
1784
1897
  function defineLogger(logger) {
1785
- return { ...logger };
1898
+ return logger;
1786
1899
  }
1787
1900
  //#endregion
1788
- //#region src/definePlugin.ts
1901
+ //#region src/definePreset.ts
1789
1902
  /**
1790
- * Wraps a plugin builder to make the options parameter optional.
1903
+ * Creates a typed preset object that bundles a name, resolvers, and optional
1904
+ * transformers — the building block for composable plugin presets.
1905
+ *
1906
+ * @example
1907
+ * import { definePreset } from '@kubb/core'
1908
+ * import { resolverTsLegacy } from '@kubb/plugin-ts'
1909
+ *
1910
+ * export const myPreset = definePreset('myPreset', { resolvers: [resolverTsLegacy] })
1911
+ *
1912
+ * @example
1913
+ * // With custom transformers
1914
+ * export const myPreset = definePreset('myPreset', { resolvers: [resolverTsLegacy], transformers: [myTransformer] })
1791
1915
  */
1792
- function definePlugin(build) {
1793
- return (options) => build(options ?? {});
1916
+ function definePreset(name, { resolvers, transformers }) {
1917
+ return {
1918
+ name,
1919
+ resolvers,
1920
+ transformers
1921
+ };
1794
1922
  }
1795
1923
  //#endregion
1796
- //#region src/PackageManager.ts
1797
- var PackageManager = class PackageManager {
1798
- static #cache = {};
1799
- #cwd;
1800
- constructor(workspace) {
1801
- if (workspace) this.#cwd = workspace;
1802
- }
1803
- set workspace(workspace) {
1804
- this.#cwd = workspace;
1805
- }
1806
- get workspace() {
1807
- return this.#cwd;
1808
- }
1809
- normalizeDirectory(directory) {
1810
- const lastChar = directory[directory.length - 1];
1811
- if (lastChar && !PATH_SEPARATORS.includes(lastChar)) return `${directory}/`;
1812
- return directory;
1813
- }
1814
- getLocation(path) {
1815
- let location = path;
1816
- if (this.#cwd) location = mod.createRequire(this.normalizeDirectory(this.#cwd)).resolve(path);
1817
- return location;
1818
- }
1819
- async import(path) {
1820
- let location = this.getLocation(path);
1821
- if (os.platform() === "win32") location = pathToFileURL(location).href;
1822
- const module = await import(location);
1823
- return module?.default ?? module;
1824
- }
1825
- async getPackageJSON() {
1826
- const pkgPath = pkg.up({ cwd: this.#cwd });
1827
- if (!pkgPath) return;
1828
- const json = await read(pkgPath);
1829
- return JSON.parse(json);
1830
- }
1831
- getPackageJSONSync() {
1832
- const pkgPath = pkg.up({ cwd: this.#cwd });
1833
- if (!pkgPath) return;
1834
- const json = readSync(pkgPath);
1835
- return JSON.parse(json);
1836
- }
1837
- static setVersion(dependency, version) {
1838
- PackageManager.#cache[dependency] = version;
1839
- }
1840
- #match(packageJSON, dependency) {
1841
- const dependencies = {
1842
- ...packageJSON.dependencies || {},
1843
- ...packageJSON.devDependencies || {}
1924
+ //#region src/definePresets.ts
1925
+ /**
1926
+ * Creates a typed presets registry object — a named collection of {@link Preset} entries.
1927
+ *
1928
+ * @example
1929
+ * import { definePreset, definePresets } from '@kubb/core'
1930
+ * import { resolverTsLegacy } from '@kubb/plugin-ts'
1931
+ *
1932
+ * export const myPresets = definePresets({
1933
+ * kubbV4: definePreset('kubbV4', { resolvers: [resolverTsLegacy] }),
1934
+ * })
1935
+ */
1936
+ function definePresets(presets) {
1937
+ return presets;
1938
+ }
1939
+ //#endregion
1940
+ //#region src/defineResolver.ts
1941
+ /**
1942
+ * Checks if an operation matches a pattern for a given filter type (`tag`, `operationId`, `path`, `method`).
1943
+ */
1944
+ function matchesOperationPattern(node, type, pattern) {
1945
+ switch (type) {
1946
+ case "tag": return node.tags.some((tag) => !!tag.match(pattern));
1947
+ case "operationId": return !!node.operationId.match(pattern);
1948
+ case "path": return !!node.path.match(pattern);
1949
+ case "method": return !!node.method.toLowerCase().match(pattern);
1950
+ default: return false;
1951
+ }
1952
+ }
1953
+ /**
1954
+ * Checks if a schema matches a pattern for a given filter type (`schemaName`).
1955
+ * Returns `null` when the filter type doesn't apply to schemas.
1956
+ */
1957
+ function matchesSchemaPattern(node, type, pattern) {
1958
+ switch (type) {
1959
+ case "schemaName": return node.name ? !!node.name.match(pattern) : false;
1960
+ default: return null;
1961
+ }
1962
+ }
1963
+ /**
1964
+ * Default name resolver — `camelCase` for most types, `PascalCase` for `type`.
1965
+ */
1966
+ function defaultResolver(name, type) {
1967
+ let resolvedName = camelCase(name);
1968
+ if (type === "file" || type === "function") resolvedName = camelCase(name, { isFile: type === "file" });
1969
+ if (type === "type") resolvedName = pascalCase(name);
1970
+ return resolvedName;
1971
+ }
1972
+ /**
1973
+ * Default option resolver — applies include/exclude filters and merges any matching override options.
1974
+ * Returns `null` when the node is filtered out.
1975
+ */
1976
+ function defaultResolveOptions(node, { options, exclude = [], include, override = [] }) {
1977
+ if (isOperationNode(node)) {
1978
+ if (exclude.some(({ type, pattern }) => matchesOperationPattern(node, type, pattern))) return null;
1979
+ if (include && !include.some(({ type, pattern }) => matchesOperationPattern(node, type, pattern))) return null;
1980
+ const overrideOptions = override.find(({ type, pattern }) => matchesOperationPattern(node, type, pattern))?.options;
1981
+ return {
1982
+ ...options,
1983
+ ...overrideOptions
1844
1984
  };
1845
- if (typeof dependency === "string" && dependencies[dependency]) return dependencies[dependency];
1846
- const matchedDependency = Object.keys(dependencies).find((dep) => dep.match(dependency));
1847
- return matchedDependency ? dependencies[matchedDependency] : void 0;
1848
- }
1849
- async getVersion(dependency) {
1850
- if (typeof dependency === "string" && PackageManager.#cache[dependency]) return PackageManager.#cache[dependency];
1851
- const packageJSON = await this.getPackageJSON();
1852
- if (!packageJSON) return;
1853
- return this.#match(packageJSON, dependency);
1854
- }
1855
- getVersionSync(dependency) {
1856
- if (typeof dependency === "string" && PackageManager.#cache[dependency]) return PackageManager.#cache[dependency];
1857
- const packageJSON = this.getPackageJSONSync();
1858
- if (!packageJSON) return;
1859
- return this.#match(packageJSON, dependency);
1860
- }
1861
- async isValid(dependency, version) {
1862
- const packageVersion = await this.getVersion(dependency);
1863
- if (!packageVersion) return false;
1864
- if (packageVersion === version) return true;
1865
- const semVer = coerce(packageVersion);
1866
- if (!semVer) return false;
1867
- return satisfies(semVer, version);
1868
- }
1869
- isValidSync(dependency, version) {
1870
- const packageVersion = this.getVersionSync(dependency);
1871
- if (!packageVersion) return false;
1872
- if (packageVersion === version) return true;
1873
- const semVer = coerce(packageVersion);
1874
- if (!semVer) return false;
1875
- return satisfies(semVer, version);
1876
1985
  }
1877
- };
1986
+ if (isSchemaNode(node)) {
1987
+ if (exclude.some(({ type, pattern }) => matchesSchemaPattern(node, type, pattern) === true)) return null;
1988
+ if (include) {
1989
+ const applicable = include.map(({ type, pattern }) => matchesSchemaPattern(node, type, pattern)).filter((r) => r !== null);
1990
+ if (applicable.length > 0 && !applicable.includes(true)) return null;
1991
+ }
1992
+ const overrideOptions = override.find(({ type, pattern }) => matchesSchemaPattern(node, type, pattern) === true)?.options;
1993
+ return {
1994
+ ...options,
1995
+ ...overrideOptions
1996
+ };
1997
+ }
1998
+ return options;
1999
+ }
2000
+ /**
2001
+ * Defines a resolver for a plugin, with built-in defaults for name casing and include/exclude/override filtering.
2002
+ * Override `default` or `resolveOptions` in the builder to customize the behavior.
2003
+ *
2004
+ * @example
2005
+ * export const resolver = defineResolver<PluginTs>(() => ({
2006
+ * name: 'default',
2007
+ * resolveName(name) {
2008
+ * return this.default(name, 'function')
2009
+ * },
2010
+ * resolveTypedName(name) {
2011
+ * return this.default(name, 'type')
2012
+ * },
2013
+ * resolveParamName(node, param) {
2014
+ * return this.resolveName(`${node.operationId} ${param.in} ${param.name}`)
2015
+ * },
2016
+ * }))
2017
+ */
2018
+ function defineResolver(build) {
2019
+ return {
2020
+ default: defaultResolver,
2021
+ resolveOptions: defaultResolveOptions,
2022
+ ...build()
2023
+ };
2024
+ }
2025
+ //#endregion
2026
+ //#region src/renderNode.tsx
2027
+ /**
2028
+ * Renders a React component for a list of operation nodes (V2 generators).
2029
+ */
2030
+ async function renderOperations(nodes, options) {
2031
+ const { config, fabric, plugin, Component, adapter } = options;
2032
+ if (!Component) return;
2033
+ const fabricChild = createReactFabric();
2034
+ await fabricChild.render(/* @__PURE__ */ jsx(Fabric, {
2035
+ meta: { plugin },
2036
+ children: /* @__PURE__ */ jsx(Component, {
2037
+ config,
2038
+ adapter,
2039
+ nodes,
2040
+ options: options.options
2041
+ })
2042
+ }));
2043
+ fabric.context.fileManager.upsert(...fabricChild.files);
2044
+ fabricChild.unmount();
2045
+ }
2046
+ /**
2047
+ * Renders a React component for a single operation node (V2 generators).
2048
+ */
2049
+ async function renderOperation(node, options) {
2050
+ const { config, fabric, plugin, Component, adapter, driver, mode } = options;
2051
+ if (!Component) return;
2052
+ const fabricChild = createReactFabric();
2053
+ await fabricChild.render(/* @__PURE__ */ jsx(Fabric, {
2054
+ meta: {
2055
+ plugin,
2056
+ driver,
2057
+ mode
2058
+ },
2059
+ children: /* @__PURE__ */ jsx(Component, {
2060
+ config,
2061
+ adapter,
2062
+ node,
2063
+ options: options.options
2064
+ })
2065
+ }));
2066
+ fabric.context.fileManager.upsert(...fabricChild.files);
2067
+ fabricChild.unmount();
2068
+ }
2069
+ /**
2070
+ * Renders a React component for a single schema node (V2 generators).
2071
+ */
2072
+ async function renderSchema(node, options) {
2073
+ const { config, fabric, plugin, Component, adapter, driver, mode } = options;
2074
+ if (!Component) return;
2075
+ const fabricChild = createReactFabric();
2076
+ await fabricChild.render(/* @__PURE__ */ jsx(Fabric, {
2077
+ meta: {
2078
+ plugin,
2079
+ driver,
2080
+ mode
2081
+ },
2082
+ children: /* @__PURE__ */ jsx(Component, {
2083
+ config,
2084
+ adapter,
2085
+ node,
2086
+ options: options.options
2087
+ })
2088
+ }));
2089
+ fabric.context.fileManager.upsert(...fabricChild.files);
2090
+ fabricChild.unmount();
2091
+ }
1878
2092
  //#endregion
1879
2093
  //#region src/storages/memoryStorage.ts
1880
2094
  /**
@@ -1894,7 +2108,7 @@ var PackageManager = class PackageManager {
1894
2108
  * })
1895
2109
  * ```
1896
2110
  */
1897
- const memoryStorage = defineStorage(() => {
2111
+ const memoryStorage = createStorage(() => {
1898
2112
  const store = /* @__PURE__ */ new Map();
1899
2113
  return {
1900
2114
  name: "memory",
@@ -1926,7 +2140,7 @@ const memoryStorage = defineStorage(() => {
1926
2140
  //#endregion
1927
2141
  //#region src/utils/FunctionParams.ts
1928
2142
  /**
1929
- * @deprecated
2143
+ * @deprecated use ast package instead
1930
2144
  */
1931
2145
  var FunctionParams = class FunctionParams {
1932
2146
  #items = [];
@@ -2002,14 +2216,10 @@ var FunctionParams = class FunctionParams {
2002
2216
  //#endregion
2003
2217
  //#region src/utils/formatters.ts
2004
2218
  /**
2005
- * Check if a formatter command is available in the system.
2219
+ * Returns `true` when the given formatter is installed and callable.
2006
2220
  *
2007
- * @param formatter - The formatter to check ('biome', 'prettier', or 'oxfmt')
2008
- * @returns Promise that resolves to true if the formatter is available, false otherwise
2009
- *
2010
- * @remarks
2011
- * This function checks availability by running `<formatter> --version` command.
2012
- * All supported formatters (biome, prettier, oxfmt) implement the --version flag.
2221
+ * Availability is detected by running `<formatter> --version` and checking
2222
+ * that the process exits without error.
2013
2223
  */
2014
2224
  async function isFormatterAvailable(formatter) {
2015
2225
  try {
@@ -2020,34 +2230,39 @@ async function isFormatterAvailable(formatter) {
2020
2230
  }
2021
2231
  }
2022
2232
  /**
2023
- * Detect which formatter is available in the system.
2024
- *
2025
- * @returns Promise that resolves to the first available formatter or undefined if none are found
2233
+ * Detects the first available code formatter on the current system.
2026
2234
  *
2027
- * @remarks
2028
- * Checks in order of preference: biome, oxfmt, prettier.
2029
- * Uses the `--version` flag to detect if each formatter command is available.
2030
- * This is a reliable method as all supported formatters implement this flag.
2235
+ * - Checks in preference order: `biome`, `oxfmt`, `prettier`.
2236
+ * - Returns `null` when none are found.
2031
2237
  *
2032
2238
  * @example
2033
- * ```typescript
2239
+ * ```ts
2034
2240
  * const formatter = await detectFormatter()
2035
2241
  * if (formatter) {
2036
2242
  * console.log(`Using ${formatter} for formatting`)
2037
- * } else {
2038
- * console.log('No formatter found')
2039
2243
  * }
2040
2244
  * ```
2041
2245
  */
2042
2246
  async function detectFormatter() {
2043
- for (const formatter of [
2247
+ const formatterNames = new Set([
2044
2248
  "biome",
2045
2249
  "oxfmt",
2046
2250
  "prettier"
2047
- ]) if (await isFormatterAvailable(formatter)) return formatter;
2251
+ ]);
2252
+ for (const formatter of formatterNames) if (await isFormatterAvailable(formatter)) return formatter;
2253
+ return null;
2048
2254
  }
2049
2255
  //#endregion
2050
2256
  //#region src/utils/TreeNode.ts
2257
+ /**
2258
+ * Tree structure used to build per-directory barrel (`index.ts`) files from a
2259
+ * flat list of generated {@link KubbFile.File} entries.
2260
+ *
2261
+ * Each node represents either a directory or a file within the output tree.
2262
+ * Use {@link TreeNode.build} to construct a root node from a file list, then
2263
+ * traverse with {@link TreeNode.forEach}, {@link TreeNode.leaves}, or the
2264
+ * `*Deep` helpers.
2265
+ */
2051
2266
  var TreeNode = class TreeNode {
2052
2267
  data;
2053
2268
  parent;
@@ -2063,10 +2278,18 @@ var TreeNode = class TreeNode {
2063
2278
  this.children.push(child);
2064
2279
  return child;
2065
2280
  }
2281
+ /**
2282
+ * Returns the root ancestor of this node, walking up via `parent` links.
2283
+ */
2066
2284
  get root() {
2067
2285
  if (!this.parent) return this;
2068
2286
  return this.parent.root;
2069
2287
  }
2288
+ /**
2289
+ * Returns all leaf descendants (nodes with no children) of this node.
2290
+ *
2291
+ * Results are cached after the first traversal.
2292
+ */
2070
2293
  get leaves() {
2071
2294
  if (!this.children || this.children.length === 0) return [this];
2072
2295
  if (this.#cachedLeaves) return this.#cachedLeaves;
@@ -2097,6 +2320,12 @@ var TreeNode = class TreeNode {
2097
2320
  if (typeof callback !== "function") throw new TypeError("map() callback must be a function");
2098
2321
  return this.leaves.map(callback);
2099
2322
  }
2323
+ /**
2324
+ * Builds a {@link TreeNode} tree from a flat list of files.
2325
+ *
2326
+ * - Filters to files under `root` (when provided) and skips `.json` files.
2327
+ * - Returns `null` when no files match.
2328
+ */
2100
2329
  static build(files, root) {
2101
2330
  try {
2102
2331
  const filteredTree = buildDirectoryTree(files, root);
@@ -2167,65 +2396,64 @@ function buildDirectoryTree(files, rootFolder = "") {
2167
2396
  return root;
2168
2397
  }
2169
2398
  //#endregion
2170
- //#region src/BarrelManager.ts
2399
+ //#region src/utils/getBarrelFiles.ts
2171
2400
  /** biome-ignore-all lint/suspicious/useIterableCallbackReturn: not needed */
2172
- var BarrelManager = class {
2173
- getFiles({ files: generatedFiles, root }) {
2174
- const cachedFiles = /* @__PURE__ */ new Map();
2175
- TreeNode.build(generatedFiles, root)?.forEach((treeNode) => {
2176
- if (!treeNode || !treeNode.children || !treeNode.parent?.data.path) return;
2177
- const barrelFile = {
2178
- path: join(treeNode.parent?.data.path, "index.ts"),
2179
- baseName: "index.ts",
2180
- exports: [],
2181
- imports: [],
2182
- sources: []
2183
- };
2184
- const previousBarrelFile = cachedFiles.get(barrelFile.path);
2185
- treeNode.leaves.forEach((item) => {
2186
- if (!item.data.name) return;
2187
- (item.data.file?.sources || []).forEach((source) => {
2188
- if (!item.data.file?.path || !source.isIndexable || !source.name) return;
2189
- if (previousBarrelFile?.sources.some((item) => item.name === source.name && item.isTypeOnly === source.isTypeOnly)) return;
2190
- barrelFile.exports.push({
2191
- name: [source.name],
2192
- path: getRelativePath(treeNode.parent?.data.path, item.data.path),
2193
- isTypeOnly: source.isTypeOnly
2194
- });
2195
- barrelFile.sources.push({
2196
- name: source.name,
2197
- isTypeOnly: source.isTypeOnly,
2198
- value: "",
2199
- isExportable: false,
2200
- isIndexable: false
2201
- });
2401
+ function getBarrelFilesByRoot(root, files) {
2402
+ const cachedFiles = /* @__PURE__ */ new Map();
2403
+ TreeNode.build(files, root)?.forEach((treeNode) => {
2404
+ if (!treeNode || !treeNode.children || !treeNode.parent?.data.path) return;
2405
+ const barrelFile = {
2406
+ path: join(treeNode.parent?.data.path, "index.ts"),
2407
+ baseName: "index.ts",
2408
+ exports: [],
2409
+ imports: [],
2410
+ sources: []
2411
+ };
2412
+ const previousBarrelFile = cachedFiles.get(barrelFile.path);
2413
+ treeNode.leaves.forEach((item) => {
2414
+ if (!item.data.name) return;
2415
+ (item.data.file?.sources || []).forEach((source) => {
2416
+ if (!item.data.file?.path || !source.isIndexable || !source.name) return;
2417
+ if (previousBarrelFile?.sources.some((item) => item.name === source.name && item.isTypeOnly === source.isTypeOnly)) return;
2418
+ barrelFile.exports.push({
2419
+ name: [source.name],
2420
+ path: getRelativePath(treeNode.parent?.data.path, item.data.path),
2421
+ isTypeOnly: source.isTypeOnly
2422
+ });
2423
+ barrelFile.sources.push({
2424
+ name: source.name,
2425
+ isTypeOnly: source.isTypeOnly,
2426
+ value: "",
2427
+ isExportable: false,
2428
+ isIndexable: false
2202
2429
  });
2203
2430
  });
2204
- if (previousBarrelFile) {
2205
- previousBarrelFile.sources.push(...barrelFile.sources);
2206
- previousBarrelFile.exports?.push(...barrelFile.exports || []);
2207
- } else cachedFiles.set(barrelFile.path, barrelFile);
2208
2431
  });
2209
- return [...cachedFiles.values()];
2210
- }
2211
- };
2212
- //#endregion
2213
- //#region src/utils/getBarrelFiles.ts
2432
+ if (previousBarrelFile) {
2433
+ previousBarrelFile.sources.push(...barrelFile.sources);
2434
+ previousBarrelFile.exports?.push(...barrelFile.exports || []);
2435
+ } else cachedFiles.set(barrelFile.path, barrelFile);
2436
+ });
2437
+ return [...cachedFiles.values()];
2438
+ }
2214
2439
  function trimExtName(text) {
2215
2440
  const dotIndex = text.lastIndexOf(".");
2216
2441
  if (dotIndex > 0 && !text.includes("/", dotIndex)) return text.slice(0, dotIndex);
2217
2442
  return text;
2218
2443
  }
2444
+ /**
2445
+ * Generates `index.ts` barrel files for all directories under `root/output.path`.
2446
+ *
2447
+ * - Returns an empty array when `type` is falsy or `'propagate'`.
2448
+ * - Skips generation when the output path itself ends with `index` (already a barrel).
2449
+ * - When `type` is `'all'`, strips named exports so every re-export becomes a wildcard (`export * from`).
2450
+ * - Attaches `meta` to each barrel file for downstream plugin identification.
2451
+ */
2219
2452
  async function getBarrelFiles(files, { type, meta = {}, root, output }) {
2220
2453
  if (!type || type === "propagate") return [];
2221
- const barrelManager = new BarrelManager();
2222
2454
  const pathToBuildFrom = join(root, output.path);
2223
2455
  if (trimExtName(pathToBuildFrom).endsWith("index")) return [];
2224
- const barrelFiles = barrelManager.getFiles({
2225
- files,
2226
- root: pathToBuildFrom,
2227
- meta
2228
- });
2456
+ const barrelFiles = getBarrelFilesByRoot(pathToBuildFrom, files);
2229
2457
  if (type === "all") return barrelFiles.map((file) => {
2230
2458
  return {
2231
2459
  ...file,
@@ -2245,38 +2473,58 @@ async function getBarrelFiles(files, { type, meta = {}, root, output }) {
2245
2473
  });
2246
2474
  }
2247
2475
  //#endregion
2248
- //#region src/utils/getPlugins.ts
2249
- function isJSONPlugins(plugins) {
2250
- return Array.isArray(plugins) && plugins.some((plugin) => Array.isArray(plugin) && typeof plugin[0] === "string");
2251
- }
2252
- function isObjectPlugins(plugins) {
2253
- return plugins instanceof Object && !Array.isArray(plugins);
2476
+ //#region src/utils/getConfigs.ts
2477
+ /**
2478
+ * Resolves a {@link ConfigInput} into a normalized array of {@link Config} objects.
2479
+ *
2480
+ * - Awaits the config when it is a `Promise`.
2481
+ * - Calls the factory function with `args` when the config is a function.
2482
+ * - Wraps a single config object in an array for uniform downstream handling.
2483
+ */
2484
+ async function getConfigs(config, args) {
2485
+ const resolved = await (typeof config === "function" ? config(args) : config);
2486
+ return (Array.isArray(resolved) ? resolved : [resolved]).map((item) => ({ ...item }));
2254
2487
  }
2255
- function getPlugins(plugins) {
2256
- if (isObjectPlugins(plugins)) throw new Error("Object plugins are not supported anymore, best to use http://kubb.dev/getting-started/configure#json");
2257
- if (isJSONPlugins(plugins)) throw new Error("JSON plugins are not supported anymore, best to use http://kubb.dev/getting-started/configure#json");
2258
- return Promise.resolve(plugins);
2488
+ //#endregion
2489
+ //#region src/utils/mergeResolvers.ts
2490
+ /**
2491
+ * Merges an array of resolvers into a single resolver. Later entries override earlier ones (last wins).
2492
+ */
2493
+ function mergeResolvers(...resolvers) {
2494
+ return resolvers.reduce((acc, curr) => ({
2495
+ ...acc,
2496
+ ...curr
2497
+ }), resolvers[0]);
2259
2498
  }
2260
2499
  //#endregion
2261
- //#region src/utils/getConfigs.ts
2500
+ //#region src/utils/getPreset.ts
2262
2501
  /**
2263
- * Converting UserConfig to Config Array without a change in the object beside the JSON convert.
2502
+ * Resolves a named preset into merged resolvers and transformers.
2503
+ *
2504
+ * - Merges the preset's resolvers on top of the first (default) resolver to produce `baseResolver`.
2505
+ * - Merges any additional user-supplied resolvers on top of that to produce the final `resolver`.
2506
+ * - Concatenates preset transformers before user-supplied transformers.
2264
2507
  */
2265
- async function getConfigs(config, args) {
2266
- let userConfigs = await (typeof config === "function" ? Promise.resolve(config(args)) : Promise.resolve(config));
2267
- if (!Array.isArray(userConfigs)) userConfigs = [userConfigs];
2268
- const results = [];
2269
- for (const item of userConfigs) {
2270
- const plugins = item.plugins ? await getPlugins(item.plugins) : void 0;
2271
- results.push({
2272
- ...item,
2273
- plugins
2274
- });
2275
- }
2276
- return results;
2508
+ function getPreset(params) {
2509
+ const { preset: presetName, presets, resolvers, transformers: userTransformers } = params;
2510
+ const [defaultResolver, ...userResolvers] = resolvers;
2511
+ const preset = presets[presetName];
2512
+ const baseResolver = mergeResolvers(defaultResolver, ...preset?.resolvers ?? []);
2513
+ return {
2514
+ baseResolver,
2515
+ resolver: mergeResolvers(baseResolver, ...userResolvers ?? []),
2516
+ transformers: [...preset?.transformers ?? [], ...userTransformers ?? []],
2517
+ preset
2518
+ };
2277
2519
  }
2278
2520
  //#endregion
2279
2521
  //#region src/utils/linters.ts
2522
+ /**
2523
+ * Returns `true` when the given linter is installed and callable.
2524
+ *
2525
+ * Availability is detected by running `<linter> --version` and checking
2526
+ * that the process exits without error.
2527
+ */
2280
2528
  async function isLinterAvailable(linter) {
2281
2529
  try {
2282
2530
  await x(linter, ["--version"], { nodeOptions: { stdio: "ignore" } });
@@ -2285,14 +2533,73 @@ async function isLinterAvailable(linter) {
2285
2533
  return false;
2286
2534
  }
2287
2535
  }
2536
+ /**
2537
+ * Detects the first available linter on the current system.
2538
+ *
2539
+ * - Checks in preference order: `biome`, `oxlint`, `eslint`.
2540
+ * - Returns `null` when none are found.
2541
+ *
2542
+ * @example
2543
+ * ```ts
2544
+ * const linter = await detectLinter()
2545
+ * if (linter) {
2546
+ * console.log(`Using ${linter} for linting`)
2547
+ * }
2548
+ * ```
2549
+ */
2288
2550
  async function detectLinter() {
2289
- for (const linter of [
2551
+ const linterNames = new Set([
2290
2552
  "biome",
2291
2553
  "oxlint",
2292
2554
  "eslint"
2293
- ]) if (await isLinterAvailable(linter)) return linter;
2555
+ ]);
2556
+ for (const linter of linterNames) if (await isLinterAvailable(linter)) return linter;
2557
+ return null;
2558
+ }
2559
+ //#endregion
2560
+ //#region src/utils/packageJSON.ts
2561
+ function getPackageJSONSync(cwd) {
2562
+ const pkgPath = pkg.up({ cwd });
2563
+ if (!pkgPath) return null;
2564
+ return JSON.parse(readSync(pkgPath));
2565
+ }
2566
+ function match(packageJSON, dependency) {
2567
+ const dependencies = {
2568
+ ...packageJSON.dependencies || {},
2569
+ ...packageJSON.devDependencies || {}
2570
+ };
2571
+ if (typeof dependency === "string" && dependencies[dependency]) return dependencies[dependency];
2572
+ const matched = Object.keys(dependencies).find((dep) => dep.match(dependency));
2573
+ return matched ? dependencies[matched] ?? null : null;
2574
+ }
2575
+ function getVersionSync(dependency, cwd) {
2576
+ const packageJSON = getPackageJSONSync(cwd);
2577
+ return packageJSON ? match(packageJSON, dependency) : null;
2578
+ }
2579
+ /**
2580
+ * Returns `true` when the nearest `package.json` declares a dependency that
2581
+ * satisfies the given semver range.
2582
+ *
2583
+ * - Searches both `dependencies` and `devDependencies`.
2584
+ * - Accepts a string package name or a `RegExp` to match scoped/pattern packages.
2585
+ * - Uses `semver.satisfies` for range comparison; returns `false` when the
2586
+ * version string cannot be coerced into a valid semver.
2587
+ *
2588
+ * @example
2589
+ * ```ts
2590
+ * satisfiesDependency('react', '>=18') // true when react@18.x is installed
2591
+ * satisfiesDependency(/^@tanstack\//, '>=5') // true when any @tanstack/* >=5 is found
2592
+ * ```
2593
+ */
2594
+ function satisfiesDependency(dependency, version, cwd) {
2595
+ const packageVersion = getVersionSync(dependency, cwd);
2596
+ if (!packageVersion) return false;
2597
+ if (packageVersion === version) return true;
2598
+ const semVer = coerce(packageVersion);
2599
+ if (!semVer) return false;
2600
+ return satisfies(semVer, version);
2294
2601
  }
2295
2602
  //#endregion
2296
- export { AsyncEventEmitter, FunctionParams, PackageManager, PluginManager, PromiseManager, URLPath, build, build as default, defineAdapter, defineConfig, defineLogger, definePlugin, definePrinter, defineStorage, detectFormatter, detectLinter, formatters, fsStorage, getBarrelFiles, getConfigs, getMode, isInputPath, linters, logLevel, memoryStorage, safeBuild, setup };
2603
+ export { AsyncEventEmitter, FunctionParams, PluginDriver, URLPath, build, build as default, createAdapter, createPlugin, createStorage, defaultResolveOptions, defineConfig, defineGenerator, defineLogger, definePreset, definePresets, definePrinter, defineResolver, detectFormatter, detectLinter, formatters, fsStorage, getBarrelFiles, getConfigs, getMode, getPreset, isInputPath, linters, logLevel, memoryStorage, mergeResolvers, renderOperation, renderOperations, renderSchema, safeBuild, satisfiesDependency, setup };
2297
2604
 
2298
2605
  //# sourceMappingURL=index.js.map