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

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-CEQPafXV.d.ts} +687 -298
  2. package/dist/hooks.cjs +15 -9
  3. package/dist/hooks.cjs.map +1 -1
  4. package/dist/hooks.d.ts +11 -5
  5. package/dist/hooks.js +16 -10
  6. package/dist/hooks.js.map +1 -1
  7. package/dist/index.cjs +1131 -536
  8. package/dist/index.cjs.map +1 -1
  9. package/dist/index.d.ts +674 -89
  10. package/dist/index.js +1114 -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/defineBuilder.ts +26 -0
  22. package/src/defineGenerator.ts +137 -0
  23. package/src/defineLogger.ts +13 -3
  24. package/src/definePreset.ts +27 -0
  25. package/src/definePresets.ts +16 -0
  26. package/src/defineResolver.ts +448 -0
  27. package/src/hooks/index.ts +1 -1
  28. package/src/hooks/useDriver.ts +8 -0
  29. package/src/hooks/useMode.ts +5 -2
  30. package/src/hooks/usePlugin.ts +5 -2
  31. package/src/index.ts +21 -6
  32. package/src/renderNode.tsx +105 -0
  33. package/src/storages/fsStorage.ts +2 -2
  34. package/src/storages/memoryStorage.ts +2 -2
  35. package/src/types.ts +342 -42
  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 +52 -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.cjs CHANGED
@@ -4,7 +4,6 @@ Object.defineProperties(exports, {
4
4
  });
5
5
  const require_chunk = require("./chunk-ByKO4r7w.cjs");
6
6
  let node_events = require("node:events");
7
- let node_util = require("node:util");
8
7
  let node_fs = require("node:fs");
9
8
  let node_fs_promises = require("node:fs/promises");
10
9
  let node_path = require("node:path");
@@ -17,21 +16,28 @@ let node_perf_hooks = require("node:perf_hooks");
17
16
  let fflate = require("fflate");
18
17
  let tinyexec = require("tinyexec");
19
18
  let node_process = require("node:process");
20
- let node_module = require("node:module");
21
- node_module = require_chunk.__toESM(node_module);
22
- let node_os = require("node:os");
23
- node_os = require_chunk.__toESM(node_os);
24
- let node_url = require("node:url");
19
+ let _kubb_react_fabric_jsx_runtime = require("@kubb/react-fabric/jsx-runtime");
20
+ let remeda = require("remeda");
25
21
  let empathic_package = require("empathic/package");
26
22
  empathic_package = require_chunk.__toESM(empathic_package);
27
23
  let semver = require("semver");
28
- let remeda = require("remeda");
29
- //#region ../../internals/utils/dist/index.js
30
- /** Thrown when a plugin's configuration or input fails validation. */
24
+ //#region ../../internals/utils/src/errors.ts
25
+ /** Thrown when a plugin's configuration or input fails validation.
26
+ *
27
+ * @example
28
+ * ```ts
29
+ * throw new ValidationPluginError('Invalid config: "output.path" is required')
30
+ * ```
31
+ */
31
32
  var ValidationPluginError = class extends Error {};
32
33
  /**
33
34
  * Thrown when one or more errors occur during a Kubb build.
34
35
  * Carries the full list of underlying errors on `errors`.
36
+ *
37
+ * @example
38
+ * ```ts
39
+ * throw new BuildError('Build failed', { errors: [err1, err2] })
40
+ * ```
35
41
  */
36
42
  var BuildError = class extends Error {
37
43
  errors;
@@ -43,19 +49,34 @@ var BuildError = class extends Error {
43
49
  };
44
50
  /**
45
51
  * Coerces an unknown thrown value to an `Error` instance.
46
- * When the value is already an `Error` it is returned as-is;
47
- * otherwise a new `Error` is created whose message is `String(value)`.
52
+ * Returns the value as-is when it is already an `Error`; otherwise wraps it with `String(value)`.
53
+ *
54
+ * @example
55
+ * ```ts
56
+ * try { ... } catch(err) {
57
+ * throw new BuildError('Build failed', { cause: toError(err), errors: [] })
58
+ * }
59
+ * ```
48
60
  */
49
61
  function toError(value) {
50
62
  return value instanceof Error ? value : new Error(String(value));
51
63
  }
64
+ //#endregion
65
+ //#region ../../internals/utils/src/asyncEventEmitter.ts
52
66
  /**
53
- * A typed EventEmitter that awaits all async listeners before resolving.
67
+ * Typed `EventEmitter` that awaits all async listeners before resolving.
54
68
  * Wraps Node's `EventEmitter` with full TypeScript event-map inference.
69
+ *
70
+ * @example
71
+ * ```ts
72
+ * const emitter = new AsyncEventEmitter<{ build: [name: string] }>()
73
+ * emitter.on('build', async (name) => { console.log(name) })
74
+ * await emitter.emit('build', 'petstore') // all listeners awaited
75
+ * ```
55
76
  */
56
77
  var AsyncEventEmitter = class {
57
78
  /**
58
- * `maxListener` controls the maximum number of listeners per event before Node emits a memory-leak warning.
79
+ * Maximum number of listeners per event before Node emits a memory-leak warning.
59
80
  * @default 10
60
81
  */
61
82
  constructor(maxListener = 10) {
@@ -63,8 +84,13 @@ var AsyncEventEmitter = class {
63
84
  }
64
85
  #emitter = new node_events.EventEmitter();
65
86
  /**
66
- * Emits an event and awaits all registered listeners in parallel.
87
+ * Emits `eventName` and awaits all registered listeners in parallel.
67
88
  * Throws if any listener rejects, wrapping the cause with the event name and serialized arguments.
89
+ *
90
+ * @example
91
+ * ```ts
92
+ * await emitter.emit('build', 'petstore')
93
+ * ```
68
94
  */
69
95
  async emit(eventName, ...eventArgs) {
70
96
  const listeners = this.#emitter.listeners(eventName);
@@ -83,11 +109,25 @@ var AsyncEventEmitter = class {
83
109
  }
84
110
  }));
85
111
  }
86
- /** Registers a persistent listener for the given event. */
112
+ /**
113
+ * Registers a persistent listener for `eventName`.
114
+ *
115
+ * @example
116
+ * ```ts
117
+ * emitter.on('build', async (name) => { console.log(name) })
118
+ * ```
119
+ */
87
120
  on(eventName, handler) {
88
121
  this.#emitter.on(eventName, handler);
89
122
  }
90
- /** Registers a one-shot listener that removes itself after the first invocation. */
123
+ /**
124
+ * Registers a one-shot listener that removes itself after the first invocation.
125
+ *
126
+ * @example
127
+ * ```ts
128
+ * emitter.onOnce('build', async (name) => { console.log(name) })
129
+ * ```
130
+ */
91
131
  onOnce(eventName, handler) {
92
132
  const wrapper = (...args) => {
93
133
  this.off(eventName, wrapper);
@@ -95,15 +135,31 @@ var AsyncEventEmitter = class {
95
135
  };
96
136
  this.on(eventName, wrapper);
97
137
  }
98
- /** Removes a previously registered listener. */
138
+ /**
139
+ * Removes a previously registered listener.
140
+ *
141
+ * @example
142
+ * ```ts
143
+ * emitter.off('build', handler)
144
+ * ```
145
+ */
99
146
  off(eventName, handler) {
100
147
  this.#emitter.off(eventName, handler);
101
148
  }
102
- /** Removes all listeners from every event channel. */
149
+ /**
150
+ * Removes all listeners from every event channel.
151
+ *
152
+ * @example
153
+ * ```ts
154
+ * emitter.removeAll()
155
+ * ```
156
+ */
103
157
  removeAll() {
104
158
  this.#emitter.removeAllListeners();
105
159
  }
106
160
  };
161
+ //#endregion
162
+ //#region ../../internals/utils/src/casing.ts
107
163
  /**
108
164
  * Shared implementation for camelCase and PascalCase conversion.
109
165
  * Splits on common word boundaries (spaces, hyphens, underscores, dots, slashes, colons)
@@ -122,9 +178,12 @@ function toCamelOrPascal(text, pascal) {
122
178
  * Splits `text` on `.` and applies `transformPart` to each segment.
123
179
  * The last segment receives `isLast = true`, all earlier segments receive `false`.
124
180
  * Segments are joined with `/` to form a file path.
181
+ *
182
+ * Only splits on dots followed by a letter so that version numbers
183
+ * embedded in operationIds (e.g. `v2025.0`) are kept intact.
125
184
  */
126
185
  function applyToFileParts(text, transformPart) {
127
- const parts = text.split(".");
186
+ const parts = text.split(/\.(?=[a-zA-Z])/);
128
187
  return parts.map((part, i) => transformPart(part, i === parts.length - 1)).join("/");
129
188
  }
130
189
  /**
@@ -142,190 +201,33 @@ function camelCase(text, { isFile, prefix = "", suffix = "" } = {}) {
142
201
  } : {}));
143
202
  return toCamelOrPascal(`${prefix} ${text} ${suffix}`, false);
144
203
  }
145
- /** Returns a `CLIAdapter` with type inference. Pass a different adapter to `createCLI` to swap the CLI engine. */
146
- function defineCLIAdapter(adapter) {
147
- return adapter;
148
- }
149
204
  /**
150
- * Serializes `CommandDefinition[]` to a plain, JSON-serializable structure.
151
- * Use to expose CLI capabilities to AI agents or MCP tools.
205
+ * Converts `text` to PascalCase.
206
+ * When `isFile` is `true`, the last dot-separated segment is PascalCased and earlier segments are camelCased.
207
+ *
208
+ * @example
209
+ * pascalCase('hello-world') // 'HelloWorld'
210
+ * pascalCase('pet.petId', { isFile: true }) // 'pet/PetId'
152
211
  */
153
- function getCommandSchema(defs) {
154
- return defs.map(serializeCommand);
155
- }
156
- function serializeCommand(def) {
157
- return {
158
- name: def.name,
159
- description: def.description,
160
- arguments: def.arguments,
161
- options: serializeOptions(def.options ?? {}),
162
- subCommands: def.subCommands ? def.subCommands.map(serializeCommand) : []
163
- };
164
- }
165
- function serializeOptions(options) {
166
- return Object.entries(options).map(([name, opt]) => {
167
- return {
168
- name,
169
- flags: `${opt.short ? `-${opt.short}, ` : ""}--${name}${opt.type === "string" ? ` <${opt.hint ?? name}>` : ""}`,
170
- type: opt.type,
171
- description: opt.description,
172
- ...opt.default !== void 0 ? { default: opt.default } : {},
173
- ...opt.hint ? { hint: opt.hint } : {},
174
- ...opt.enum ? { enum: opt.enum } : {},
175
- ...opt.required ? { required: opt.required } : {}
176
- };
177
- });
178
- }
179
- /** Prints formatted help output for a command using its `CommandDefinition`. */
180
- function renderHelp(def, parentName) {
181
- const schema = getCommandSchema([def])[0];
182
- const programName = parentName ? `${parentName} ${schema.name}` : schema.name;
183
- const argsPart = schema.arguments?.length ? ` ${schema.arguments.join(" ")}` : "";
184
- const subCmdPart = schema.subCommands.length ? " <command>" : "";
185
- console.log(`\n${(0, node_util.styleText)("bold", "Usage:")} ${programName}${argsPart}${subCmdPart} [options]\n`);
186
- if (schema.description) console.log(` ${schema.description}\n`);
187
- if (schema.subCommands.length) {
188
- console.log((0, node_util.styleText)("bold", "Commands:"));
189
- for (const sub of schema.subCommands) console.log(` ${(0, node_util.styleText)("cyan", sub.name.padEnd(16))}${sub.description}`);
190
- console.log();
191
- }
192
- const options = [...schema.options, {
193
- name: "help",
194
- flags: "-h, --help",
195
- type: "boolean",
196
- description: "Show help"
197
- }];
198
- console.log((0, node_util.styleText)("bold", "Options:"));
199
- for (const opt of options) {
200
- const flags = (0, node_util.styleText)("cyan", opt.flags.padEnd(30));
201
- const defaultPart = opt.default !== void 0 ? (0, node_util.styleText)("dim", ` (default: ${opt.default})`) : "";
202
- console.log(` ${flags}${opt.description}${defaultPart}`);
203
- }
204
- console.log();
205
- }
206
- function buildParseOptions(def) {
207
- const result = { help: {
208
- type: "boolean",
209
- short: "h"
210
- } };
211
- for (const [name, opt] of Object.entries(def.options ?? {})) result[name] = {
212
- type: opt.type,
213
- ...opt.short ? { short: opt.short } : {},
214
- ...opt.default !== void 0 ? { default: opt.default } : {}
215
- };
216
- return result;
212
+ function pascalCase(text, { isFile, prefix = "", suffix = "" } = {}) {
213
+ if (isFile) return applyToFileParts(text, (part, isLast) => isLast ? pascalCase(part, {
214
+ prefix,
215
+ suffix
216
+ }) : camelCase(part));
217
+ return toCamelOrPascal(`${prefix} ${text} ${suffix}`, true);
217
218
  }
218
- async function runCommand(def, argv, parentName) {
219
- const parseOptions = buildParseOptions(def);
220
- let parsed;
221
- try {
222
- const result = (0, node_util.parseArgs)({
223
- args: argv,
224
- options: parseOptions,
225
- allowPositionals: true,
226
- strict: false
227
- });
228
- parsed = {
229
- values: result.values,
230
- positionals: result.positionals
231
- };
232
- } catch {
233
- renderHelp(def, parentName);
234
- process.exit(1);
235
- }
236
- if (parsed.values["help"]) {
237
- renderHelp(def, parentName);
238
- process.exit(0);
239
- }
240
- for (const [name, opt] of Object.entries(def.options ?? {})) if (opt.required && parsed.values[name] === void 0) {
241
- console.error((0, node_util.styleText)("red", `Error: --${name} is required`));
242
- renderHelp(def, parentName);
243
- process.exit(1);
244
- }
245
- if (!def.run) {
246
- renderHelp(def, parentName);
247
- process.exit(0);
248
- }
249
- try {
250
- await def.run(parsed);
251
- } catch (err) {
252
- console.error((0, node_util.styleText)("red", `Error: ${err instanceof Error ? err.message : String(err)}`));
253
- renderHelp(def, parentName);
254
- process.exit(1);
255
- }
256
- }
257
- function printRootHelp(programName, version, defs) {
258
- console.log(`\n${(0, node_util.styleText)("bold", "Usage:")} ${programName} <command> [options]\n`);
259
- console.log(` Kubb generation — v${version}\n`);
260
- console.log((0, node_util.styleText)("bold", "Commands:"));
261
- for (const def of defs) console.log(` ${(0, node_util.styleText)("cyan", def.name.padEnd(16))}${def.description}`);
262
- console.log();
263
- console.log((0, node_util.styleText)("bold", "Options:"));
264
- console.log(` ${(0, node_util.styleText)("cyan", "-v, --version".padEnd(30))}Show version number`);
265
- console.log(` ${(0, node_util.styleText)("cyan", "-h, --help".padEnd(30))}Show help`);
266
- console.log();
267
- console.log(`Run ${(0, node_util.styleText)("cyan", `${programName} <command> --help`)} for command-specific help.\n`);
268
- }
269
- defineCLIAdapter({
270
- renderHelp(def, parentName) {
271
- renderHelp(def, parentName);
272
- },
273
- async run(defs, argv, opts) {
274
- const { programName, defaultCommandName, version } = opts;
275
- const args = argv.length >= 2 && argv[0]?.includes("node") ? argv.slice(2) : argv;
276
- if (args[0] === "--version" || args[0] === "-v") {
277
- console.log(version);
278
- process.exit(0);
279
- }
280
- if (args[0] === "--help" || args[0] === "-h") {
281
- printRootHelp(programName, version, defs);
282
- process.exit(0);
283
- }
284
- if (args.length === 0) {
285
- const defaultDef = defs.find((d) => d.name === defaultCommandName);
286
- if (defaultDef?.run) await runCommand(defaultDef, [], programName);
287
- else printRootHelp(programName, version, defs);
288
- return;
289
- }
290
- const [first, ...rest] = args;
291
- const isKnownSubcommand = defs.some((d) => d.name === first);
292
- let def;
293
- let commandArgv;
294
- let parentName;
295
- if (isKnownSubcommand) {
296
- def = defs.find((d) => d.name === first);
297
- commandArgv = rest;
298
- parentName = programName;
299
- } else {
300
- def = defs.find((d) => d.name === defaultCommandName);
301
- commandArgv = args;
302
- parentName = programName;
303
- }
304
- if (!def) {
305
- console.error(`Unknown command: ${first}`);
306
- printRootHelp(programName, version, defs);
307
- process.exit(1);
308
- }
309
- if (def.subCommands?.length) {
310
- const [subName, ...subRest] = commandArgv;
311
- const subDef = def.subCommands.find((s) => s.name === subName);
312
- if (subName === "--help" || subName === "-h") {
313
- renderHelp(def, parentName);
314
- process.exit(0);
315
- }
316
- if (!subDef) {
317
- renderHelp(def, parentName);
318
- process.exit(subName ? 1 : 0);
319
- }
320
- await runCommand(subDef, subRest, `${parentName} ${def.name}`);
321
- return;
322
- }
323
- await runCommand(def, commandArgv, parentName);
324
- }
325
- });
219
+ //#endregion
220
+ //#region ../../internals/utils/src/time.ts
326
221
  /**
327
- * Calculates elapsed time in milliseconds from a high-resolution start time.
328
- * Rounds to 2 decimal places to provide sub-millisecond precision without noise.
222
+ * Calculates elapsed time in milliseconds from a high-resolution `process.hrtime` start time.
223
+ * Rounds to 2 decimal places for sub-millisecond precision without noise.
224
+ *
225
+ * @example
226
+ * ```ts
227
+ * const start = process.hrtime()
228
+ * doWork()
229
+ * getElapsedMs(start) // 42.35
230
+ * ```
329
231
  */
330
232
  function getElapsedMs(hrStart) {
331
233
  const [seconds, nanoseconds] = process.hrtime(hrStart);
@@ -333,39 +235,22 @@ function getElapsedMs(hrStart) {
333
235
  return Math.round(ms * 100) / 100;
334
236
  }
335
237
  /**
336
- * Converts a millisecond duration into a human-readable string.
337
- * Adjusts units (ms, s, m s) based on the magnitude of the duration.
238
+ * Converts a millisecond duration into a human-readable string (`ms`, `s`, or `m s`).
239
+ *
240
+ * @example
241
+ * ```ts
242
+ * formatMs(250) // '250ms'
243
+ * formatMs(1500) // '1.50s'
244
+ * formatMs(90000) // '1m 30.0s'
245
+ * ```
338
246
  */
339
247
  function formatMs(ms) {
340
248
  if (ms >= 6e4) return `${Math.floor(ms / 6e4)}m ${(ms % 6e4 / 1e3).toFixed(1)}s`;
341
249
  if (ms >= 1e3) return `${(ms / 1e3).toFixed(2)}s`;
342
250
  return `${Math.round(ms)}ms`;
343
251
  }
344
- /**
345
- * Parses a CSS hex color string (`#RGB`) into its RGB channels.
346
- * Falls back to `255` for any channel that cannot be parsed.
347
- */
348
- function parseHex(color) {
349
- const int = Number.parseInt(color.replace("#", ""), 16);
350
- return Number.isNaN(int) ? {
351
- r: 255,
352
- g: 255,
353
- b: 255
354
- } : {
355
- r: int >> 16 & 255,
356
- g: int >> 8 & 255,
357
- b: int & 255
358
- };
359
- }
360
- /**
361
- * Returns a function that wraps a string in a 24-bit ANSI true-color escape sequence
362
- * for the given hex color.
363
- */
364
- function hex(color) {
365
- const { r, g, b } = parseHex(color);
366
- return (text) => `\x1b[38;2;${r};${g};${b}m${text}\x1b[0m`;
367
- }
368
- hex("#F55A17"), hex("#F5A217"), hex("#F58517"), hex("#B45309"), hex("#FFFFFF"), hex("#adadc6"), hex("#FDA4AF");
252
+ //#endregion
253
+ //#region ../../internals/utils/src/fs.ts
369
254
  /**
370
255
  * Converts all backslashes to forward slashes.
371
256
  * Extended-length Windows paths (`\\?\...`) are left unchanged.
@@ -375,8 +260,14 @@ function toSlash(p) {
375
260
  return p.replaceAll("\\", "/");
376
261
  }
377
262
  /**
378
- * Returns the relative path from `rootDir` to `filePath`, always using
379
- * forward slashes and prefixed with `./` when not already traversing upward.
263
+ * Returns the relative path from `rootDir` to `filePath`, always using forward slashes
264
+ * and prefixed with `./` when not already traversing upward.
265
+ *
266
+ * @example
267
+ * ```ts
268
+ * getRelativePath('/src/components', '/src/components/Button.tsx') // './Button.tsx'
269
+ * getRelativePath('/src/components', '/src/utils/helpers.ts') // '../utils/helpers.ts'
270
+ * ```
380
271
  */
381
272
  function getRelativePath(rootDir, filePath) {
382
273
  if (!rootDir || !filePath) throw new Error(`Root and file should be filled in when retrieving the relativePath, ${rootDir || ""} ${filePath || ""}`);
@@ -386,43 +277,54 @@ function getRelativePath(rootDir, filePath) {
386
277
  /**
387
278
  * Resolves to `true` when the file or directory at `path` exists.
388
279
  * Uses `Bun.file().exists()` when running under Bun, `fs.access` otherwise.
280
+ *
281
+ * @example
282
+ * ```ts
283
+ * if (await exists('./kubb.config.ts')) {
284
+ * const content = await read('./kubb.config.ts')
285
+ * }
286
+ * ```
389
287
  */
390
288
  async function exists(path) {
391
289
  if (typeof Bun !== "undefined") return Bun.file(path).exists();
392
290
  return (0, node_fs_promises.access)(path).then(() => true, () => false);
393
291
  }
394
292
  /**
395
- * Reads the file at `path` as a UTF-8 string.
396
- * Uses `Bun.file().text()` when running under Bun, `fs.readFile` otherwise.
293
+ * Synchronous counterpart of `read`.
294
+ *
295
+ * @example
296
+ * ```ts
297
+ * const source = readSync('./src/Pet.ts')
298
+ * ```
397
299
  */
398
- async function read(path) {
399
- if (typeof Bun !== "undefined") return Bun.file(path).text();
400
- return (0, node_fs_promises.readFile)(path, { encoding: "utf8" });
401
- }
402
- /** Synchronous counterpart of `read`. */
403
300
  function readSync(path) {
404
301
  return (0, node_fs.readFileSync)(path, { encoding: "utf8" });
405
302
  }
406
303
  /**
407
304
  * Writes `data` to `path`, trimming leading/trailing whitespace before saving.
408
- * Skips the write and returns `undefined` when the trimmed content is empty or
409
- * identical to what is already on disk.
305
+ * Skips the write when the trimmed content is empty or identical to what is already on disk.
410
306
  * Creates any missing parent directories automatically.
411
- * When `sanity` is `true`, re-reads the file after writing and throws if the
412
- * content does not match.
307
+ * When `sanity` is `true`, re-reads the file after writing and throws if the content does not match.
308
+ *
309
+ * @example
310
+ * ```ts
311
+ * await write('./src/Pet.ts', source) // writes and returns trimmed content
312
+ * await write('./src/Pet.ts', source) // null — file unchanged
313
+ * await write('./src/Pet.ts', ' ') // null — empty content skipped
314
+ * ```
413
315
  */
414
316
  async function write(path, data, options = {}) {
415
317
  const trimmed = data.trim();
416
- if (trimmed === "") return void 0;
318
+ if (trimmed === "") return null;
417
319
  const resolved = (0, node_path.resolve)(path);
418
320
  if (typeof Bun !== "undefined") {
419
321
  const file = Bun.file(resolved);
420
- if ((await file.exists() ? await file.text() : null) === trimmed) return void 0;
322
+ if ((await file.exists() ? await file.text() : null) === trimmed) return null;
421
323
  await Bun.write(resolved, trimmed);
422
324
  return trimmed;
423
325
  }
424
326
  try {
425
- if (await (0, node_fs_promises.readFile)(resolved, { encoding: "utf-8" }) === trimmed) return void 0;
327
+ if (await (0, node_fs_promises.readFile)(resolved, { encoding: "utf-8" }) === trimmed) return null;
426
328
  } catch {}
427
329
  await (0, node_fs_promises.mkdir)((0, node_path.dirname)(resolved), { recursive: true });
428
330
  await (0, node_fs_promises.writeFile)(resolved, trimmed, { encoding: "utf-8" });
@@ -433,16 +335,32 @@ async function write(path, data, options = {}) {
433
335
  }
434
336
  return trimmed;
435
337
  }
436
- /** Recursively removes `path`. Silently succeeds when `path` does not exist. */
338
+ /**
339
+ * Recursively removes `path`. Silently succeeds when `path` does not exist.
340
+ *
341
+ * @example
342
+ * ```ts
343
+ * await clean('./dist')
344
+ * ```
345
+ */
437
346
  async function clean(path) {
438
347
  return (0, node_fs_promises.rm)(path, {
439
348
  recursive: true,
440
349
  force: true
441
350
  });
442
351
  }
352
+ //#endregion
353
+ //#region ../../internals/utils/src/names.ts
443
354
  /**
444
355
  * Registers `originalName` in `data` without altering the returned name.
445
- * Use this when you need to track usage frequency but always emit the original identifier.
356
+ * Use when you need to track usage frequency but always emit the original identifier.
357
+ *
358
+ * @example
359
+ * ```ts
360
+ * const seen: Record<string, number> = {}
361
+ * setUniqueName('Foo', seen) // 'Foo' (seen = { Foo: 1 })
362
+ * setUniqueName('Foo', seen) // 'Foo' (seen = { Foo: 2 })
363
+ * ```
446
364
  */
447
365
  function setUniqueName(originalName, data) {
448
366
  let used = data[originalName] || 0;
@@ -453,11 +371,26 @@ function setUniqueName(originalName, data) {
453
371
  data[originalName] = 1;
454
372
  return originalName;
455
373
  }
374
+ //#endregion
375
+ //#region ../../internals/utils/src/promise.ts
376
+ /** Returns `true` when `result` is a rejected `Promise.allSettled` result with a typed `reason`.
377
+ *
378
+ * @example
379
+ * ```ts
380
+ * const results = await Promise.allSettled([p1, p2])
381
+ * results.filter(isPromiseRejectedResult<Error>).map((r) => r.reason.message)
382
+ * ```
383
+ */
384
+ function isPromiseRejectedResult(result) {
385
+ return result.status === "rejected";
386
+ }
387
+ //#endregion
388
+ //#region ../../internals/utils/src/reserved.ts
456
389
  /**
457
390
  * JavaScript and Java reserved words.
458
391
  * @link https://github.com/jonschlinkert/reserved/blob/master/index.js
459
392
  */
460
- const reservedWords = [
393
+ const reservedWords = new Set([
461
394
  "abstract",
462
395
  "arguments",
463
396
  "boolean",
@@ -539,18 +472,31 @@ const reservedWords = [
539
472
  "toString",
540
473
  "undefined",
541
474
  "valueOf"
542
- ];
475
+ ]);
543
476
  /**
544
- * Prefixes a word with `_` when it is a reserved JavaScript/Java identifier
545
- * or starts with a digit.
477
+ * Prefixes `word` with `_` when it is a reserved JavaScript/Java identifier or starts with a digit.
478
+ *
479
+ * @example
480
+ * ```ts
481
+ * transformReservedWord('class') // '_class'
482
+ * transformReservedWord('42foo') // '_42foo'
483
+ * transformReservedWord('status') // 'status'
484
+ * ```
546
485
  */
547
486
  function transformReservedWord(word) {
548
487
  const firstChar = word.charCodeAt(0);
549
- if (word && (reservedWords.includes(word) || firstChar >= 48 && firstChar <= 57)) return `_${word}`;
488
+ if (word && (reservedWords.has(word) || firstChar >= 48 && firstChar <= 57)) return `_${word}`;
550
489
  return word;
551
490
  }
552
491
  /**
553
492
  * Returns `true` when `name` is a syntactically valid JavaScript variable name.
493
+ *
494
+ * @example
495
+ * ```ts
496
+ * isValidVarName('status') // true
497
+ * isValidVarName('class') // false (reserved word)
498
+ * isValidVarName('42foo') // false (starts with digit)
499
+ * ```
554
500
  */
555
501
  function isValidVarName(name) {
556
502
  try {
@@ -560,6 +506,8 @@ function isValidVarName(name) {
560
506
  }
561
507
  return true;
562
508
  }
509
+ //#endregion
510
+ //#region ../../internals/utils/src/urlPath.ts
563
511
  /**
564
512
  * Parses and transforms an OpenAPI/Swagger path string into various URL formats.
565
513
  *
@@ -569,18 +517,33 @@ function isValidVarName(name) {
569
517
  * p.template // '`/pet/${petId}`'
570
518
  */
571
519
  var URLPath = class {
572
- /** The raw OpenAPI/Swagger path string, e.g. `/pet/{petId}`. */
520
+ /**
521
+ * The raw OpenAPI/Swagger path string, e.g. `/pet/{petId}`.
522
+ */
573
523
  path;
574
524
  #options;
575
525
  constructor(path, options = {}) {
576
526
  this.path = path;
577
527
  this.#options = options;
578
528
  }
579
- /** Converts the OpenAPI path to Express-style colon syntax, e.g. `/pet/{petId}` → `/pet/:petId`. */
529
+ /** Converts the OpenAPI path to Express-style colon syntax, e.g. `/pet/{petId}` → `/pet/:petId`.
530
+ *
531
+ * @example
532
+ * ```ts
533
+ * new URLPath('/pet/{petId}').URL // '/pet/:petId'
534
+ * ```
535
+ */
580
536
  get URL() {
581
537
  return this.toURLPath();
582
538
  }
583
- /** Returns `true` when `path` is a fully-qualified URL (e.g. starts with `https://`). */
539
+ /** Returns `true` when `path` is a fully-qualified URL (e.g. starts with `https://`).
540
+ *
541
+ * @example
542
+ * ```ts
543
+ * new URLPath('https://petstore.swagger.io/v2/pet').isURL // true
544
+ * new URLPath('/pet/{petId}').isURL // false
545
+ * ```
546
+ */
584
547
  get isURL() {
585
548
  try {
586
549
  return !!new URL(this.path).href;
@@ -598,11 +561,25 @@ var URLPath = class {
598
561
  get template() {
599
562
  return this.toTemplateString();
600
563
  }
601
- /** Returns the path and its extracted params as a structured `URLObject`, or as a stringified expression when `stringify` is set. */
564
+ /** Returns the path and its extracted params as a structured `URLObject`, or as a stringified expression when `stringify` is set.
565
+ *
566
+ * @example
567
+ * ```ts
568
+ * new URLPath('/pet/{petId}').object
569
+ * // { url: '/pet/:petId', params: { petId: 'petId' } }
570
+ * ```
571
+ */
602
572
  get object() {
603
573
  return this.toObject();
604
574
  }
605
- /** Returns a map of path parameter names, or `undefined` when the path has no parameters. */
575
+ /** Returns a map of path parameter names, or `undefined` when the path has no parameters.
576
+ *
577
+ * @example
578
+ * ```ts
579
+ * new URLPath('/pet/{petId}').params // { petId: 'petId' }
580
+ * new URLPath('/pet').params // undefined
581
+ * ```
582
+ */
606
583
  get params() {
607
584
  return this.getParams();
608
585
  }
@@ -610,7 +587,9 @@ var URLPath = class {
610
587
  const param = isValidVarName(raw) ? raw : camelCase(raw);
611
588
  return this.#options.casing === "camelcase" ? camelCase(param) : param;
612
589
  }
613
- /** Iterates over every `{param}` token in `path`, calling `fn` with the raw token and transformed name. */
590
+ /**
591
+ * Iterates over every `{param}` token in `path`, calling `fn` with the raw token and transformed name.
592
+ */
614
593
  #eachParam(fn) {
615
594
  for (const match of this.path.matchAll(/\{([^}]+)\}/g)) {
616
595
  const raw = match[1];
@@ -647,6 +626,12 @@ var URLPath = class {
647
626
  * Extracts all `{param}` segments from the path and returns them as a key-value map.
648
627
  * An optional `replacer` transforms each parameter name in both key and value positions.
649
628
  * Returns `undefined` when no path parameters are found.
629
+ *
630
+ * @example
631
+ * ```ts
632
+ * new URLPath('/pet/{petId}/tag/{tagId}').getParams()
633
+ * // { petId: 'petId', tagId: 'tagId' }
634
+ * ```
650
635
  */
651
636
  getParams(replacer) {
652
637
  const params = {};
@@ -656,7 +641,13 @@ var URLPath = class {
656
641
  });
657
642
  return Object.keys(params).length > 0 ? params : void 0;
658
643
  }
659
- /** Converts the OpenAPI path to Express-style colon syntax, e.g. `/pet/{petId}` → `/pet/:petId`. */
644
+ /** Converts the OpenAPI path to Express-style colon syntax.
645
+ *
646
+ * @example
647
+ * ```ts
648
+ * new URLPath('/pet/{petId}').toURLPath() // '/pet/:petId'
649
+ * ```
650
+ */
660
651
  toURLPath() {
661
652
  return this.path.replace(/\{([^}]+)\}/g, ":$1");
662
653
  }
@@ -674,11 +665,27 @@ function isInputPath(config) {
674
665
  }
675
666
  //#endregion
676
667
  //#region src/constants.ts
668
+ /**
669
+ * Base URL for the Kubb Studio web app.
670
+ */
677
671
  const DEFAULT_STUDIO_URL = "https://studio.kubb.dev";
672
+ /**
673
+ * File name used for generated barrel (index) files.
674
+ */
678
675
  const BARREL_FILENAME = "index.ts";
676
+ /**
677
+ * Default banner style written at the top of every generated file.
678
+ */
679
679
  const DEFAULT_BANNER = "simple";
680
+ /**
681
+ * Default file-extension mapping used when no explicit mapping is configured.
682
+ */
680
683
  const DEFAULT_EXTENSION = { ".ts": ".ts" };
681
- const PATH_SEPARATORS = ["/", "\\"];
684
+ /**
685
+ * Numeric log-level thresholds used internally to compare verbosity.
686
+ *
687
+ * Higher numbers are more verbose.
688
+ */
682
689
  const logLevel = {
683
690
  silent: Number.NEGATIVE_INFINITY,
684
691
  error: 0,
@@ -687,6 +694,13 @@ const logLevel = {
687
694
  verbose: 4,
688
695
  debug: 5
689
696
  };
697
+ /**
698
+ * CLI command descriptors for each supported linter.
699
+ *
700
+ * Each entry contains the executable `command`, an `args` factory that maps an
701
+ * output path to the correct argument list, and an `errorMessage` shown when
702
+ * the linter is not found.
703
+ */
690
704
  const linters = {
691
705
  eslint: {
692
706
  command: "eslint",
@@ -708,6 +722,13 @@ const linters = {
708
722
  errorMessage: "Oxlint not found"
709
723
  }
710
724
  };
725
+ /**
726
+ * CLI command descriptors for each supported code formatter.
727
+ *
728
+ * Each entry contains the executable `command`, an `args` factory that maps an
729
+ * output path to the correct argument list, and an `errorMessage` shown when
730
+ * the formatter is not found.
731
+ */
711
732
  const formatters = {
712
733
  prettier: {
713
734
  command: "prettier",
@@ -907,7 +928,11 @@ function validateConcurrency(concurrency) {
907
928
  //#endregion
908
929
  //#region src/utils/executeStrategies.ts
909
930
  /**
910
- * Chains promises
931
+ * Runs promise functions in sequence, threading each result into the next call.
932
+ *
933
+ * - Each function receives the accumulated state from the previous call.
934
+ * - Skips functions that return a falsy value (acts as a no-op for that step).
935
+ * - Returns an array of all individual results.
911
936
  */
912
937
  function hookSeq(promises) {
913
938
  return promises.filter(Boolean).reduce((promise, func) => {
@@ -920,7 +945,10 @@ function hookSeq(promises) {
920
945
  }, Promise.resolve([]));
921
946
  }
922
947
  /**
923
- * Chains promises, first non-null result stops and returns
948
+ * Runs promise functions in sequence and returns the first non-null result.
949
+ *
950
+ * - Stops as soon as `nullCheck` passes for a result (default: `!== null`).
951
+ * - Subsequent functions are skipped once a match is found.
924
952
  */
925
953
  function hookFirst(promises, nullCheck = (state) => state !== null) {
926
954
  let promise = Promise.resolve(null);
@@ -931,7 +959,10 @@ function hookFirst(promises, nullCheck = (state) => state !== null) {
931
959
  return promise;
932
960
  }
933
961
  /**
934
- * Runs an array of promise functions with optional concurrency limit.
962
+ * Runs promise functions concurrently and returns all settled results.
963
+ *
964
+ * - Limits simultaneous executions to `concurrency` (default: unlimited).
965
+ * - Uses `Promise.allSettled` so individual failures do not cancel other tasks.
935
966
  */
936
967
  function hookParallel(promises, concurrency = Number.POSITIVE_INFINITY) {
937
968
  const limit = pLimit(concurrency);
@@ -939,29 +970,13 @@ function hookParallel(promises, concurrency = Number.POSITIVE_INFINITY) {
939
970
  return Promise.allSettled(tasks);
940
971
  }
941
972
  //#endregion
942
- //#region src/PromiseManager.ts
943
- var PromiseManager = class {
944
- #options = {};
945
- constructor(options = {}) {
946
- this.#options = options;
947
- }
948
- run(strategy, promises, { concurrency = Number.POSITIVE_INFINITY } = {}) {
949
- if (strategy === "seq") return hookSeq(promises);
950
- if (strategy === "first") return hookFirst(promises, this.#options.nullCheck);
951
- if (strategy === "parallel") return hookParallel(promises, concurrency);
952
- throw new Error(`${strategy} not implemented`);
953
- }
954
- };
955
- function isPromiseRejectedResult(result) {
956
- return result.status === "rejected";
957
- }
958
- //#endregion
959
- //#region src/PluginManager.ts
973
+ //#region src/PluginDriver.ts
960
974
  function getMode(fileOrFolder) {
961
975
  if (!fileOrFolder) return "split";
962
- return node_path.default.extname(fileOrFolder) ? "single" : "split";
976
+ return (0, node_path.extname)(fileOrFolder) ? "single" : "split";
963
977
  }
964
- var PluginManager = class {
978
+ const hookFirstNullCheck = (state) => !!state?.result;
979
+ var PluginDriver = class {
965
980
  config;
966
981
  options;
967
982
  /**
@@ -969,14 +984,13 @@ var PluginManager = class {
969
984
  * the build pipeline after the adapter's `parse()` resolves.
970
985
  */
971
986
  rootNode = void 0;
987
+ adapter = void 0;
972
988
  #studioIsOpen = false;
973
989
  #plugins = /* @__PURE__ */ new Set();
974
990
  #usedPluginNames = {};
975
- #promiseManager;
976
991
  constructor(config, options) {
977
992
  this.config = config;
978
993
  this.options = options;
979
- this.#promiseManager = new PromiseManager({ nullCheck: (state) => !!state?.result });
980
994
  [...config.plugins || []].forEach((plugin) => {
981
995
  const parsedPlugin = this.#parse(plugin);
982
996
  this.#plugins.add(parsedPlugin);
@@ -987,14 +1001,14 @@ var PluginManager = class {
987
1001
  }
988
1002
  getContext(plugin) {
989
1003
  const plugins = [...this.#plugins];
990
- const pluginManager = this;
1004
+ const driver = this;
991
1005
  const baseContext = {
992
1006
  fabric: this.options.fabric,
993
1007
  config: this.config,
994
1008
  plugin,
995
1009
  events: this.options.events,
996
- pluginManager: this,
997
- mode: getMode(node_path.default.resolve(this.config.root, this.config.output.path)),
1010
+ driver: this,
1011
+ mode: getMode((0, node_path.resolve)(this.config.root, this.config.output.path)),
998
1012
  addFile: async (...files) => {
999
1013
  await this.options.fabric.addFile(...files);
1000
1014
  },
@@ -1002,15 +1016,18 @@ var PluginManager = class {
1002
1016
  await this.options.fabric.upsertFile(...files);
1003
1017
  },
1004
1018
  get rootNode() {
1005
- return pluginManager.rootNode;
1019
+ return driver.rootNode;
1020
+ },
1021
+ get adapter() {
1022
+ return driver.adapter;
1006
1023
  },
1007
1024
  openInStudio(options) {
1008
- if (typeof pluginManager.config.devtools !== "object") throw new Error("Devtools must be an object");
1009
- if (!pluginManager.rootNode) throw new Error("RootNode is not defined, make sure you have set the parser in kubb.config.ts");
1010
- if (pluginManager.#studioIsOpen) return;
1011
- pluginManager.#studioIsOpen = true;
1012
- const studioUrl = pluginManager.config.devtools?.studioUrl ?? "https://studio.kubb.dev";
1013
- return openInStudio(pluginManager.rootNode, studioUrl, options);
1025
+ if (!driver.config.devtools || driver.#studioIsOpen) return;
1026
+ if (typeof driver.config.devtools !== "object") throw new Error("Devtools must be an object");
1027
+ if (!driver.rootNode || !driver.adapter) throw new Error("adapter is not defined, make sure you have set the parser in kubb.config.ts");
1028
+ driver.#studioIsOpen = true;
1029
+ const studioUrl = driver.config.devtools?.studioUrl ?? "https://studio.kubb.dev";
1030
+ return openInStudio(driver.rootNode, studioUrl, options);
1014
1031
  }
1015
1032
  };
1016
1033
  const mergedExtras = {};
@@ -1027,17 +1044,21 @@ var PluginManager = class {
1027
1044
  return this.#getSortedPlugins();
1028
1045
  }
1029
1046
  getFile({ name, mode, extname, pluginName, options }) {
1030
- const baseName = `${name}${extname}`;
1047
+ const resolvedName = mode ? mode === "single" ? "" : this.resolveName({
1048
+ name,
1049
+ pluginName,
1050
+ type: "file"
1051
+ }) : name;
1031
1052
  const path = this.resolvePath({
1032
- baseName,
1053
+ baseName: `${resolvedName}${extname}`,
1033
1054
  mode,
1034
1055
  pluginName,
1035
1056
  options
1036
1057
  });
1037
- if (!path) throw new Error(`Filepath should be defined for resolvedName "${name}" and pluginName "${pluginName}"`);
1058
+ if (!path) throw new Error(`Filepath should be defined for resolvedName "${resolvedName}" and pluginName "${pluginName}"`);
1038
1059
  return {
1039
1060
  path,
1040
- baseName,
1061
+ baseName: (0, node_path.basename)(path),
1041
1062
  meta: { pluginName },
1042
1063
  sources: [],
1043
1064
  imports: [],
@@ -1045,8 +1066,7 @@ var PluginManager = class {
1045
1066
  };
1046
1067
  }
1047
1068
  resolvePath = (params) => {
1048
- const root = node_path.default.resolve(this.config.root, this.config.output.path);
1049
- const defaultPath = node_path.default.resolve(root, params.baseName);
1069
+ const defaultPath = (0, node_path.resolve)((0, node_path.resolve)(this.config.root, this.config.output.path), params.baseName);
1050
1070
  if (params.pluginName) return this.hookForPluginSync({
1051
1071
  pluginName: params.pluginName,
1052
1072
  hookName: "resolvePath",
@@ -1126,7 +1146,7 @@ var PluginManager = class {
1126
1146
  hookName,
1127
1147
  plugins
1128
1148
  });
1129
- const promises = plugins.map((plugin) => {
1149
+ const result = await hookFirst(plugins.map((plugin) => {
1130
1150
  return async () => {
1131
1151
  const value = await this.#execute({
1132
1152
  strategy: "hookFirst",
@@ -1139,8 +1159,7 @@ var PluginManager = class {
1139
1159
  result: value
1140
1160
  });
1141
1161
  };
1142
- });
1143
- const result = await this.#promiseManager.run("first", promises);
1162
+ }), hookFirstNullCheck);
1144
1163
  this.events.emit("plugins:hook:progress:end", { hookName });
1145
1164
  return result;
1146
1165
  }
@@ -1176,7 +1195,7 @@ var PluginManager = class {
1176
1195
  plugins
1177
1196
  });
1178
1197
  const pluginStartTimes = /* @__PURE__ */ new Map();
1179
- const promises = plugins.map((plugin) => {
1198
+ const results = await hookParallel(plugins.map((plugin) => {
1180
1199
  return () => {
1181
1200
  pluginStartTimes.set(plugin, node_perf_hooks.performance.now());
1182
1201
  return this.#execute({
@@ -1186,8 +1205,7 @@ var PluginManager = class {
1186
1205
  plugin
1187
1206
  });
1188
1207
  };
1189
- });
1190
- const results = await this.#promiseManager.run("parallel", promises, { concurrency: this.options.concurrency });
1208
+ }), this.options.concurrency);
1191
1209
  results.forEach((result, index) => {
1192
1210
  if (isPromiseRejectedResult(result)) {
1193
1211
  const plugin = this.#getSortedPlugins(hookName)[index];
@@ -1218,15 +1236,14 @@ var PluginManager = class {
1218
1236
  hookName,
1219
1237
  plugins
1220
1238
  });
1221
- const promises = plugins.map((plugin) => {
1239
+ await hookSeq(plugins.map((plugin) => {
1222
1240
  return () => this.#execute({
1223
1241
  strategy: "hookSeq",
1224
1242
  hookName,
1225
1243
  parameters,
1226
1244
  plugin
1227
1245
  });
1228
- });
1229
- await this.#promiseManager.run("seq", promises);
1246
+ }));
1230
1247
  this.events.emit("plugins:hook:progress:end", { hookName });
1231
1248
  }
1232
1249
  #getSortedPlugins(hookName) {
@@ -1234,7 +1251,8 @@ var PluginManager = class {
1234
1251
  if (hookName) return plugins.filter((plugin) => hookName in plugin);
1235
1252
  return plugins.map((plugin) => {
1236
1253
  if (plugin.pre) {
1237
- const missingPlugins = plugin.pre.filter((pluginName) => !plugins.find((pluginToFind) => pluginToFind.name === pluginName));
1254
+ let missingPlugins = plugin.pre.filter((pluginName) => !plugins.find((pluginToFind) => pluginToFind.name === pluginName));
1255
+ if (missingPlugins.includes("plugin-oas") && this.adapter) missingPlugins = missingPlugins.filter((pluginName) => pluginName !== "plugin-oas");
1238
1256
  if (missingPlugins.length > 0) throw new ValidationPluginError(`The plugin '${plugin.name}' has a pre set that references missing plugins for '${missingPlugins.join(", ")}'`);
1239
1257
  }
1240
1258
  return plugin;
@@ -1354,19 +1372,12 @@ var PluginManager = class {
1354
1372
  }
1355
1373
  };
1356
1374
  //#endregion
1357
- //#region src/defineStorage.ts
1375
+ //#region src/createStorage.ts
1358
1376
  /**
1359
- * Wraps a storage builder so the `options` argument is optional, following the
1360
- * same factory pattern as `definePlugin`, `defineLogger`, and `defineAdapter`.
1361
- *
1362
- * The builder receives the resolved options object and must return a
1363
- * `DefineStorage`-compatible object that includes a `name` string.
1377
+ * Creates a storage factory. Call the returned function with optional options to get the storage instance.
1364
1378
  *
1365
1379
  * @example
1366
- * ```ts
1367
- * import { defineStorage } from '@kubb/core'
1368
- *
1369
- * export const memoryStorage = defineStorage((_options) => {
1380
+ * export const memoryStorage = createStorage(() => {
1370
1381
  * const store = new Map<string, string>()
1371
1382
  * return {
1372
1383
  * name: 'memory',
@@ -1374,13 +1385,15 @@ var PluginManager = class {
1374
1385
  * async getItem(key) { return store.get(key) ?? null },
1375
1386
  * async setItem(key, value) { store.set(key, value) },
1376
1387
  * async removeItem(key) { store.delete(key) },
1377
- * async getKeys() { return [...store.keys()] },
1378
- * async clear() { store.clear() },
1388
+ * async getKeys(base) {
1389
+ * const keys = [...store.keys()]
1390
+ * return base ? keys.filter((k) => k.startsWith(base)) : keys
1391
+ * },
1392
+ * async clear(base) { if (!base) store.clear() },
1379
1393
  * }
1380
1394
  * })
1381
- * ```
1382
1395
  */
1383
- function defineStorage(build) {
1396
+ function createStorage(build) {
1384
1397
  return (options) => build(options ?? {});
1385
1398
  }
1386
1399
  //#endregion
@@ -1408,7 +1421,7 @@ function defineStorage(build) {
1408
1421
  * })
1409
1422
  * ```
1410
1423
  */
1411
- const fsStorage = defineStorage(() => ({
1424
+ const fsStorage = createStorage(() => ({
1412
1425
  name: "fs",
1413
1426
  async hasItem(key) {
1414
1427
  try {
@@ -1456,11 +1469,14 @@ const fsStorage = defineStorage(() => ({
1456
1469
  }));
1457
1470
  //#endregion
1458
1471
  //#region package.json
1459
- var version = "5.0.0-alpha.2";
1472
+ var version = "5.0.0-alpha.21";
1460
1473
  //#endregion
1461
1474
  //#region src/utils/diagnostics.ts
1462
1475
  /**
1463
- * Get diagnostic information for debugging
1476
+ * Returns a snapshot of the current runtime environment.
1477
+ *
1478
+ * Useful for attaching context to debug logs and error reports so that
1479
+ * issues can be reproduced without manual information gathering.
1464
1480
  */
1465
1481
  function getDiagnosticInfo() {
1466
1482
  return {
@@ -1473,6 +1489,17 @@ function getDiagnosticInfo() {
1473
1489
  }
1474
1490
  //#endregion
1475
1491
  //#region src/build.ts
1492
+ /**
1493
+ * Initializes all Kubb infrastructure for a build without executing any plugins.
1494
+ *
1495
+ * - Validates the input path (when applicable).
1496
+ * - Applies config defaults (`root`, `output.*`, `devtools`).
1497
+ * - Creates the Fabric instance and wires storage, format, and lint hooks.
1498
+ * - Runs the adapter (if configured) to produce the universal `RootNode`.
1499
+ *
1500
+ * Pass the returned {@link SetupResult} directly to {@link safeBuild} or {@link build}
1501
+ * via the `overrides` argument to reuse the same infrastructure across multiple runs.
1502
+ */
1476
1503
  async function setup(options) {
1477
1504
  const { config: userConfig, events = new AsyncEventEmitter() } = options;
1478
1505
  const sources = /* @__PURE__ */ new Map();
@@ -1570,7 +1597,7 @@ async function setup(options) {
1570
1597
  ` • Barrel type: ${definedConfig.output.barrelType || "none"}`
1571
1598
  ]
1572
1599
  });
1573
- const pluginManager = new PluginManager(definedConfig, {
1600
+ const pluginDriver = new PluginDriver(definedConfig, {
1574
1601
  fabric,
1575
1602
  events,
1576
1603
  concurrency: 15
@@ -1581,25 +1608,32 @@ async function setup(options) {
1581
1608
  date: /* @__PURE__ */ new Date(),
1582
1609
  logs: [`Running adapter: ${definedConfig.adapter.name}`]
1583
1610
  });
1584
- pluginManager.rootNode = await definedConfig.adapter.parse(source);
1611
+ pluginDriver.adapter = definedConfig.adapter;
1612
+ pluginDriver.rootNode = await definedConfig.adapter.parse(source);
1585
1613
  await events.emit("debug", {
1586
1614
  date: /* @__PURE__ */ new Date(),
1587
1615
  logs: [
1588
1616
  `✓ Adapter '${definedConfig.adapter.name}' resolved RootNode`,
1589
- ` • Schemas: ${pluginManager.rootNode.schemas.length}`,
1590
- ` • Operations: ${pluginManager.rootNode.operations.length}`
1617
+ ` • Schemas: ${pluginDriver.rootNode.schemas.length}`,
1618
+ ` • Operations: ${pluginDriver.rootNode.operations.length}`
1591
1619
  ]
1592
1620
  });
1593
1621
  }
1594
1622
  return {
1595
1623
  events,
1596
1624
  fabric,
1597
- pluginManager,
1625
+ driver: pluginDriver,
1598
1626
  sources
1599
1627
  };
1600
1628
  }
1629
+ /**
1630
+ * Runs a full Kubb build and throws on any error or plugin failure.
1631
+ *
1632
+ * Internally delegates to {@link safeBuild} and rethrows collected errors.
1633
+ * Pass an existing {@link SetupResult} via `overrides` to skip the setup phase.
1634
+ */
1601
1635
  async function build(options, overrides) {
1602
- const { fabric, files, pluginManager, failedPlugins, pluginTimings, error, sources } = await safeBuild(options, overrides);
1636
+ const { fabric, files, driver, failedPlugins, pluginTimings, error, sources } = await safeBuild(options, overrides);
1603
1637
  if (error) throw error;
1604
1638
  if (failedPlugins.size > 0) {
1605
1639
  const errors = [...failedPlugins].map(({ error }) => error);
@@ -1609,20 +1643,30 @@ async function build(options, overrides) {
1609
1643
  failedPlugins,
1610
1644
  fabric,
1611
1645
  files,
1612
- pluginManager,
1646
+ driver,
1613
1647
  pluginTimings,
1614
1648
  error: void 0,
1615
1649
  sources
1616
1650
  };
1617
1651
  }
1652
+ /**
1653
+ * Runs a full Kubb build and captures errors instead of throwing.
1654
+ *
1655
+ * - Installs each plugin in order, recording failures in `failedPlugins`.
1656
+ * - Generates the root barrel file when `output.barrelType` is set.
1657
+ * - Writes all files through Fabric.
1658
+ *
1659
+ * Returns a {@link BuildOutput} even on failure — inspect `error` and
1660
+ * `failedPlugins` to determine whether the build succeeded.
1661
+ */
1618
1662
  async function safeBuild(options, overrides) {
1619
- const { fabric, pluginManager, events, sources } = overrides ? overrides : await setup(options);
1663
+ const { fabric, driver, events, sources } = overrides ? overrides : await setup(options);
1620
1664
  const failedPlugins = /* @__PURE__ */ new Set();
1621
1665
  const pluginTimings = /* @__PURE__ */ new Map();
1622
- const config = pluginManager.config;
1666
+ const config = driver.config;
1623
1667
  try {
1624
- for (const plugin of pluginManager.plugins) {
1625
- const context = pluginManager.getContext(plugin);
1668
+ for (const plugin of driver.plugins) {
1669
+ const context = driver.getContext(plugin);
1626
1670
  const hrStart = process.hrtime();
1627
1671
  const installer = plugin.install.bind(context);
1628
1672
  try {
@@ -1695,7 +1739,7 @@ async function safeBuild(options, overrides) {
1695
1739
  rootDir,
1696
1740
  existingExports: new Set(existingBarrel?.exports?.flatMap((e) => Array.isArray(e.name) ? e.name : [e.name]).filter((n) => Boolean(n)) ?? []),
1697
1741
  config,
1698
- pluginManager
1742
+ driver
1699
1743
  }),
1700
1744
  sources: [],
1701
1745
  imports: [],
@@ -1713,7 +1757,7 @@ async function safeBuild(options, overrides) {
1713
1757
  failedPlugins,
1714
1758
  fabric,
1715
1759
  files,
1716
- pluginManager,
1760
+ driver,
1717
1761
  pluginTimings,
1718
1762
  sources
1719
1763
  };
@@ -1722,16 +1766,16 @@ async function safeBuild(options, overrides) {
1722
1766
  failedPlugins,
1723
1767
  fabric,
1724
1768
  files: [],
1725
- pluginManager,
1769
+ driver,
1726
1770
  pluginTimings,
1727
1771
  error,
1728
1772
  sources
1729
1773
  };
1730
1774
  }
1731
1775
  }
1732
- function buildBarrelExports({ barrelFiles, rootDir, existingExports, config, pluginManager }) {
1776
+ function buildBarrelExports({ barrelFiles, rootDir, existingExports, config, driver }) {
1733
1777
  const pluginNameMap = /* @__PURE__ */ new Map();
1734
- for (const plugin of pluginManager.plugins) pluginNameMap.set(plugin.name, plugin);
1778
+ for (const plugin of driver.plugins) pluginNameMap.set(plugin.name, plugin);
1735
1779
  return barrelFiles.flatMap((file) => {
1736
1780
  const containsOnlyTypes = file.sources?.every((source) => source.isTypeOnly);
1737
1781
  return (file.sources ?? []).flatMap((source) => {
@@ -1756,133 +1800,572 @@ function buildBarrelExports({ barrelFiles, rootDir, existingExports, config, plu
1756
1800
  function inputToAdapterSource(config) {
1757
1801
  if (Array.isArray(config.input)) return {
1758
1802
  type: "paths",
1759
- paths: config.input.map((i) => (0, node_path.resolve)(config.root, i.path))
1803
+ paths: config.input.map((i) => new URLPath(i.path).isURL ? i.path : (0, node_path.resolve)(config.root, i.path))
1760
1804
  };
1761
1805
  if ("data" in config.input) return {
1762
1806
  type: "data",
1763
1807
  data: config.input.data
1764
1808
  };
1809
+ if (new URLPath(config.input.path).isURL) return {
1810
+ type: "path",
1811
+ path: config.input.path
1812
+ };
1765
1813
  return {
1766
1814
  type: "path",
1767
1815
  path: (0, node_path.resolve)(config.root, config.input.path)
1768
1816
  };
1769
1817
  }
1770
1818
  //#endregion
1771
- //#region src/defineAdapter.ts
1819
+ //#region src/createAdapter.ts
1772
1820
  /**
1773
- * Wraps an adapter builder to make the options parameter optional.
1821
+ * Creates an adapter factory. Call the returned function with optional options to get the adapter instance.
1774
1822
  *
1775
1823
  * @example
1776
- * ```ts
1777
- * export const adapterOas = defineAdapter<OasAdapter>((options) => {
1778
- * const { validate = true, dateType = 'string' } = options
1824
+ * export const myAdapter = createAdapter<MyAdapter>((options) => {
1779
1825
  * return {
1780
- * name: adapterOasName,
1781
- * options: { validate, dateType, ... },
1782
- * parse(source) { ... },
1826
+ * name: 'my-adapter',
1827
+ * options,
1828
+ * async parse(source) { ... },
1783
1829
  * }
1784
1830
  * })
1785
- * ```
1831
+ *
1832
+ * // instantiate
1833
+ * const adapter = myAdapter({ validate: true })
1786
1834
  */
1787
- function defineAdapter(build) {
1835
+ function createAdapter(build) {
1788
1836
  return (options) => build(options ?? {});
1789
1837
  }
1790
1838
  //#endregion
1839
+ //#region src/createPlugin.ts
1840
+ /**
1841
+ * Creates a plugin factory. Call the returned function with optional options to get the plugin instance.
1842
+ *
1843
+ * @example
1844
+ * export const myPlugin = createPlugin<MyPlugin>((options) => {
1845
+ * return {
1846
+ * name: 'my-plugin',
1847
+ * options,
1848
+ * resolvePath(baseName) { ... },
1849
+ * resolveName(name, type) { ... },
1850
+ * }
1851
+ * })
1852
+ *
1853
+ * // instantiate
1854
+ * const plugin = myPlugin({ output: { path: 'src/gen' } })
1855
+ */
1856
+ function createPlugin(build) {
1857
+ return (options) => build(options ?? {});
1858
+ }
1859
+ //#endregion
1860
+ //#region src/defineBuilder.ts
1861
+ /**
1862
+ * Defines a builder for a plugin — a named collection of schema-building helpers that
1863
+ * can be exported alongside the plugin and imported by other plugins or generators.
1864
+ *
1865
+ * @example
1866
+ * export const builder = defineBuilder<PluginTs>(() => ({
1867
+ * name: 'default',
1868
+ * buildParamsSchema({ params, node, resolver }) {
1869
+ * return createSchema({ type: 'object', properties: [] })
1870
+ * },
1871
+ * buildDataSchemaNode({ node, resolver }) {
1872
+ * return createSchema({ type: 'object', properties: [] })
1873
+ * },
1874
+ * }))
1875
+ */
1876
+ function defineBuilder(build) {
1877
+ return build();
1878
+ }
1879
+ //#endregion
1880
+ //#region src/defineGenerator.ts
1881
+ function defineGenerator(generator) {
1882
+ if (generator.type === "react") return {
1883
+ version: "2",
1884
+ Operations() {
1885
+ return null;
1886
+ },
1887
+ Operation() {
1888
+ return null;
1889
+ },
1890
+ Schema() {
1891
+ return null;
1892
+ },
1893
+ ...generator
1894
+ };
1895
+ return {
1896
+ version: "2",
1897
+ async operations() {
1898
+ return [];
1899
+ },
1900
+ async operation() {
1901
+ return [];
1902
+ },
1903
+ async schema() {
1904
+ return [];
1905
+ },
1906
+ ...generator
1907
+ };
1908
+ }
1909
+ //#endregion
1791
1910
  //#region src/defineLogger.ts
1911
+ /**
1912
+ * Wraps a logger definition into a typed {@link Logger}.
1913
+ *
1914
+ * @example
1915
+ * export const myLogger = defineLogger({
1916
+ * name: 'my-logger',
1917
+ * install(context, options) {
1918
+ * context.on('info', (message) => console.log('ℹ', message))
1919
+ * context.on('error', (error) => console.error('✗', error.message))
1920
+ * },
1921
+ * })
1922
+ */
1792
1923
  function defineLogger(logger) {
1793
- return { ...logger };
1924
+ return logger;
1794
1925
  }
1795
1926
  //#endregion
1796
- //#region src/definePlugin.ts
1927
+ //#region src/definePreset.ts
1797
1928
  /**
1798
- * Wraps a plugin builder to make the options parameter optional.
1929
+ * Creates a typed preset object that bundles a name, resolvers, optional
1930
+ * transformers, and optional generators — the building block for composable plugin presets.
1931
+ *
1932
+ * @example
1933
+ * import { definePreset } from '@kubb/core'
1934
+ * import { resolverTsLegacy } from '@kubb/plugin-ts'
1935
+ *
1936
+ * export const myPreset = definePreset('myPreset', { resolvers: [resolverTsLegacy] })
1937
+ *
1938
+ * @example
1939
+ * // With custom transformers
1940
+ * export const myPreset = definePreset('myPreset', { resolvers: [resolverTsLegacy], transformers: [myTransformer] })
1941
+ *
1942
+ * @example
1943
+ * // With generators
1944
+ * export const myPreset = definePreset('myPreset', { resolvers: [resolverTsLegacy], generators: [typeGeneratorLegacy] })
1799
1945
  */
1800
- function definePlugin(build) {
1801
- return (options) => build(options ?? {});
1946
+ function definePreset(name, { resolvers, transformers, generators }) {
1947
+ return {
1948
+ name,
1949
+ resolvers,
1950
+ transformers,
1951
+ generators
1952
+ };
1953
+ }
1954
+ //#endregion
1955
+ //#region src/definePresets.ts
1956
+ /**
1957
+ * Creates a typed presets registry object — a named collection of {@link Preset} entries.
1958
+ *
1959
+ * @example
1960
+ * import { definePreset, definePresets } from '@kubb/core'
1961
+ * import { resolverTsLegacy } from '@kubb/plugin-ts'
1962
+ *
1963
+ * export const myPresets = definePresets({
1964
+ * kubbV4: definePreset('kubbV4', { resolvers: [resolverTsLegacy] }),
1965
+ * })
1966
+ */
1967
+ function definePresets(presets) {
1968
+ return presets;
1802
1969
  }
1803
1970
  //#endregion
1804
- //#region src/PackageManager.ts
1805
- var PackageManager = class PackageManager {
1806
- static #cache = {};
1807
- #cwd;
1808
- constructor(workspace) {
1809
- if (workspace) this.#cwd = workspace;
1810
- }
1811
- set workspace(workspace) {
1812
- this.#cwd = workspace;
1813
- }
1814
- get workspace() {
1815
- return this.#cwd;
1816
- }
1817
- normalizeDirectory(directory) {
1818
- const lastChar = directory[directory.length - 1];
1819
- if (lastChar && !PATH_SEPARATORS.includes(lastChar)) return `${directory}/`;
1820
- return directory;
1821
- }
1822
- getLocation(path) {
1823
- let location = path;
1824
- if (this.#cwd) location = node_module.default.createRequire(this.normalizeDirectory(this.#cwd)).resolve(path);
1825
- return location;
1826
- }
1827
- async import(path) {
1828
- let location = this.getLocation(path);
1829
- if (node_os.default.platform() === "win32") location = (0, node_url.pathToFileURL)(location).href;
1830
- const module = await import(location);
1831
- return module?.default ?? module;
1832
- }
1833
- async getPackageJSON() {
1834
- const pkgPath = empathic_package.up({ cwd: this.#cwd });
1835
- if (!pkgPath) return;
1836
- const json = await read(pkgPath);
1837
- return JSON.parse(json);
1838
- }
1839
- getPackageJSONSync() {
1840
- const pkgPath = empathic_package.up({ cwd: this.#cwd });
1841
- if (!pkgPath) return;
1842
- const json = readSync(pkgPath);
1843
- return JSON.parse(json);
1844
- }
1845
- static setVersion(dependency, version) {
1846
- PackageManager.#cache[dependency] = version;
1847
- }
1848
- #match(packageJSON, dependency) {
1849
- const dependencies = {
1850
- ...packageJSON.dependencies || {},
1851
- ...packageJSON.devDependencies || {}
1971
+ //#region src/defineResolver.ts
1972
+ /**
1973
+ * Checks if an operation matches a pattern for a given filter type (`tag`, `operationId`, `path`, `method`).
1974
+ */
1975
+ function matchesOperationPattern(node, type, pattern) {
1976
+ switch (type) {
1977
+ case "tag": return node.tags.some((tag) => !!tag.match(pattern));
1978
+ case "operationId": return !!node.operationId.match(pattern);
1979
+ case "path": return !!node.path.match(pattern);
1980
+ case "method": return !!node.method.toLowerCase().match(pattern);
1981
+ default: return false;
1982
+ }
1983
+ }
1984
+ /**
1985
+ * Checks if a schema matches a pattern for a given filter type (`schemaName`).
1986
+ *
1987
+ * Returns `null` when the filter type doesn't apply to schemas.
1988
+ */
1989
+ function matchesSchemaPattern(node, type, pattern) {
1990
+ switch (type) {
1991
+ case "schemaName": return node.name ? !!node.name.match(pattern) : false;
1992
+ default: return null;
1993
+ }
1994
+ }
1995
+ /**
1996
+ * Default name resolver used by `defineResolver`.
1997
+ *
1998
+ * - `camelCase` for `function` and `file` types.
1999
+ * - `PascalCase` for `type`.
2000
+ * - `camelCase` for everything else.
2001
+ */
2002
+ function defaultResolver(name, type) {
2003
+ let resolvedName = camelCase(name);
2004
+ if (type === "file" || type === "function") resolvedName = camelCase(name, { isFile: type === "file" });
2005
+ if (type === "type") resolvedName = pascalCase(name);
2006
+ return resolvedName;
2007
+ }
2008
+ /**
2009
+ * Default option resolver — applies include/exclude filters and merges matching override options.
2010
+ *
2011
+ * Returns `null` when the node is filtered out by an `exclude` rule or not matched by any `include` rule.
2012
+ *
2013
+ * @example Include/exclude filtering
2014
+ * ```ts
2015
+ * const options = defaultResolveOptions(operationNode, {
2016
+ * options: { output: 'types' },
2017
+ * exclude: [{ type: 'tag', pattern: 'internal' }],
2018
+ * })
2019
+ * // → null when node has tag 'internal'
2020
+ * ```
2021
+ *
2022
+ * @example Override merging
2023
+ * ```ts
2024
+ * const options = defaultResolveOptions(operationNode, {
2025
+ * options: { enumType: 'asConst' },
2026
+ * override: [{ type: 'operationId', pattern: 'listPets', options: { enumType: 'enum' } }],
2027
+ * })
2028
+ * // → { enumType: 'enum' } when operationId matches
2029
+ * ```
2030
+ */
2031
+ function defaultResolveOptions(node, { options, exclude = [], include, override = [] }) {
2032
+ if ((0, _kubb_ast.isOperationNode)(node)) {
2033
+ if (exclude.some(({ type, pattern }) => matchesOperationPattern(node, type, pattern))) return null;
2034
+ if (include && !include.some(({ type, pattern }) => matchesOperationPattern(node, type, pattern))) return null;
2035
+ const overrideOptions = override.find(({ type, pattern }) => matchesOperationPattern(node, type, pattern))?.options;
2036
+ return {
2037
+ ...options,
2038
+ ...overrideOptions
1852
2039
  };
1853
- if (typeof dependency === "string" && dependencies[dependency]) return dependencies[dependency];
1854
- const matchedDependency = Object.keys(dependencies).find((dep) => dep.match(dependency));
1855
- return matchedDependency ? dependencies[matchedDependency] : void 0;
1856
- }
1857
- async getVersion(dependency) {
1858
- if (typeof dependency === "string" && PackageManager.#cache[dependency]) return PackageManager.#cache[dependency];
1859
- const packageJSON = await this.getPackageJSON();
1860
- if (!packageJSON) return;
1861
- return this.#match(packageJSON, dependency);
1862
- }
1863
- getVersionSync(dependency) {
1864
- if (typeof dependency === "string" && PackageManager.#cache[dependency]) return PackageManager.#cache[dependency];
1865
- const packageJSON = this.getPackageJSONSync();
1866
- if (!packageJSON) return;
1867
- return this.#match(packageJSON, dependency);
1868
- }
1869
- async isValid(dependency, version) {
1870
- const packageVersion = await this.getVersion(dependency);
1871
- if (!packageVersion) return false;
1872
- if (packageVersion === version) return true;
1873
- const semVer = (0, semver.coerce)(packageVersion);
1874
- if (!semVer) return false;
1875
- return (0, semver.satisfies)(semVer, version);
1876
- }
1877
- isValidSync(dependency, version) {
1878
- const packageVersion = this.getVersionSync(dependency);
1879
- if (!packageVersion) return false;
1880
- if (packageVersion === version) return true;
1881
- const semVer = (0, semver.coerce)(packageVersion);
1882
- if (!semVer) return false;
1883
- return (0, semver.satisfies)(semVer, version);
1884
2040
  }
1885
- };
2041
+ if ((0, _kubb_ast.isSchemaNode)(node)) {
2042
+ if (exclude.some(({ type, pattern }) => matchesSchemaPattern(node, type, pattern) === true)) return null;
2043
+ if (include) {
2044
+ const applicable = include.map(({ type, pattern }) => matchesSchemaPattern(node, type, pattern)).filter((r) => r !== null);
2045
+ if (applicable.length > 0 && !applicable.includes(true)) return null;
2046
+ }
2047
+ const overrideOptions = override.find(({ type, pattern }) => matchesSchemaPattern(node, type, pattern) === true)?.options;
2048
+ return {
2049
+ ...options,
2050
+ ...overrideOptions
2051
+ };
2052
+ }
2053
+ return options;
2054
+ }
2055
+ /**
2056
+ * Default path resolver used by `defineResolver`.
2057
+ *
2058
+ * - Returns the output directory in `single` mode.
2059
+ * - Resolves into a tag- or path-based subdirectory when `group` and a `tag`/`path` value are provided.
2060
+ * - Falls back to a flat `output/baseName` path otherwise.
2061
+ *
2062
+ * A custom `group.name` function overrides the default subdirectory naming.
2063
+ * For `tag` groups the default is `${camelCase(tag)}Controller`.
2064
+ * For `path` groups the default is the first path segment after `/`.
2065
+ *
2066
+ * @example Flat output
2067
+ * ```ts
2068
+ * defaultResolvePath({ baseName: 'petTypes.ts' }, { root: '/src', output: { path: 'types' } })
2069
+ * // → '/src/types/petTypes.ts'
2070
+ * ```
2071
+ *
2072
+ * @example Tag-based grouping
2073
+ * ```ts
2074
+ * defaultResolvePath(
2075
+ * { baseName: 'petTypes.ts', tag: 'pets' },
2076
+ * { root: '/src', output: { path: 'types' }, group: { type: 'tag' } },
2077
+ * )
2078
+ * // → '/src/types/petsController/petTypes.ts'
2079
+ * ```
2080
+ *
2081
+ * @example Path-based grouping
2082
+ * ```ts
2083
+ * defaultResolvePath(
2084
+ * { baseName: 'petTypes.ts', path: '/pets/list' },
2085
+ * { root: '/src', output: { path: 'types' }, group: { type: 'path' } },
2086
+ * )
2087
+ * // → '/src/types/pets/petTypes.ts'
2088
+ * ```
2089
+ *
2090
+ * @example Single-file mode
2091
+ * ```ts
2092
+ * defaultResolvePath(
2093
+ * { baseName: 'petTypes.ts', pathMode: 'single' },
2094
+ * { root: '/src', output: { path: 'types' } },
2095
+ * )
2096
+ * // → '/src/types'
2097
+ * ```
2098
+ */
2099
+ function defaultResolvePath({ baseName, pathMode, tag, path: groupPath }, { root, output, group }) {
2100
+ if ((pathMode ?? getMode(node_path.default.resolve(root, output.path))) === "single") return node_path.default.resolve(root, output.path);
2101
+ if (group && (groupPath || tag)) {
2102
+ const groupName = group.name ? group.name : (ctx) => {
2103
+ if (group.type === "path") return `${ctx.group.split("/")[1]}`;
2104
+ return `${camelCase(ctx.group)}Controller`;
2105
+ };
2106
+ return node_path.default.resolve(root, output.path, groupName({ group: group.type === "path" ? groupPath : tag }), baseName);
2107
+ }
2108
+ return node_path.default.resolve(root, output.path, baseName);
2109
+ }
2110
+ /**
2111
+ * Default file resolver used by `defineResolver`.
2112
+ *
2113
+ * Resolves a `KubbFile.File` by combining name resolution (`resolver.default`) with
2114
+ * path resolution (`resolver.resolvePath`). The resolved file always has empty
2115
+ * `sources`, `imports`, and `exports` arrays — consumers populate those separately.
2116
+ *
2117
+ * In `single` mode the name is omitted and the file sits directly in the output directory.
2118
+ *
2119
+ * @example Resolve a schema file
2120
+ * ```ts
2121
+ * const file = defaultResolveFile.call(resolver,
2122
+ * { name: 'pet', extname: '.ts' },
2123
+ * { root: '/src', output: { path: 'types' } },
2124
+ * )
2125
+ * // → { baseName: 'pet.ts', path: '/src/types/pet.ts', sources: [], ... }
2126
+ * ```
2127
+ *
2128
+ * @example Resolve an operation file with tag grouping
2129
+ * ```ts
2130
+ * const file = defaultResolveFile.call(resolver,
2131
+ * { name: 'listPets', extname: '.ts', tag: 'pets' },
2132
+ * { root: '/src', output: { path: 'types' }, group: { type: 'tag' } },
2133
+ * )
2134
+ * // → { baseName: 'listPets.ts', path: '/src/types/petsController/listPets.ts', ... }
2135
+ * ```
2136
+ */
2137
+ function defaultResolveFile({ name, extname, tag, path: groupPath }, context) {
2138
+ const pathMode = getMode(node_path.default.resolve(context.root, context.output.path));
2139
+ const baseName = `${pathMode === "single" ? "" : this.default(name, "file")}${extname}`;
2140
+ const filePath = this.resolvePath({
2141
+ baseName,
2142
+ pathMode,
2143
+ tag,
2144
+ path: groupPath
2145
+ }, context);
2146
+ return {
2147
+ path: filePath,
2148
+ baseName: node_path.default.basename(filePath),
2149
+ meta: { pluginName: this.pluginName },
2150
+ sources: [],
2151
+ imports: [],
2152
+ exports: []
2153
+ };
2154
+ }
2155
+ /**
2156
+ * Generates the default "Generated by Kubb" banner from config and optional node metadata.
2157
+ */
2158
+ function buildDefaultBanner({ title, description, version, config }) {
2159
+ try {
2160
+ let source = "";
2161
+ if (Array.isArray(config.input)) {
2162
+ const first = config.input[0];
2163
+ if (first && "path" in first) source = node_path.default.basename(first.path);
2164
+ } else if ("path" in config.input) source = node_path.default.basename(config.input.path);
2165
+ else if ("data" in config.input) source = "text content";
2166
+ let banner = "/**\n* Generated by Kubb (https://kubb.dev/).\n* Do not edit manually.\n";
2167
+ if (config.output.defaultBanner === "simple") {
2168
+ banner += "*/\n";
2169
+ return banner;
2170
+ }
2171
+ if (source) banner += `* Source: ${source}\n`;
2172
+ if (title) banner += `* Title: ${title}\n`;
2173
+ if (description) {
2174
+ const formattedDescription = description.replace(/\n/gm, "\n* ");
2175
+ banner += `* Description: ${formattedDescription}\n`;
2176
+ }
2177
+ if (version) banner += `* OpenAPI spec version: ${version}\n`;
2178
+ banner += "*/\n";
2179
+ return banner;
2180
+ } catch (_error) {
2181
+ return "/**\n* Generated by Kubb (https://kubb.dev/).\n* Do not edit manually.\n*/";
2182
+ }
2183
+ }
2184
+ /**
2185
+ * Default banner resolver — returns the banner string for a generated file.
2186
+ *
2187
+ * - When `output.banner` is a function and `node` is provided, calls it with the node.
2188
+ * - When `output.banner` is a function and `node` is absent, falls back to the default Kubb banner.
2189
+ * - When `output.banner` is a string, returns it directly.
2190
+ * - When `config.output.defaultBanner` is `false`, returns `undefined`.
2191
+ * - Otherwise returns the default "Generated by Kubb" banner.
2192
+ *
2193
+ * @example String banner
2194
+ * ```ts
2195
+ * defaultResolveBanner(undefined, { output: { banner: '// my banner' }, config })
2196
+ * // → '// my banner'
2197
+ * ```
2198
+ *
2199
+ * @example Function banner with node
2200
+ * ```ts
2201
+ * defaultResolveBanner(rootNode, { output: { banner: (node) => `// v${node.version}` }, config })
2202
+ * // → '// v3.0.0'
2203
+ * ```
2204
+ *
2205
+ * @example Disabled banner
2206
+ * ```ts
2207
+ * defaultResolveBanner(undefined, { config: { output: { defaultBanner: false }, ...config } })
2208
+ * // → undefined
2209
+ * ```
2210
+ */
2211
+ function defaultResolveBanner(node, { output, config }) {
2212
+ if (typeof output?.banner === "function") return node ? output.banner(node) : buildDefaultBanner({ config });
2213
+ if (typeof output?.banner === "string") return output.banner;
2214
+ if (config.output.defaultBanner === false) return;
2215
+ return buildDefaultBanner({ config });
2216
+ }
2217
+ /**
2218
+ * Default footer resolver — returns the footer string for a generated file.
2219
+ *
2220
+ * - When `output.footer` is a function and `node` is provided, calls it with the node.
2221
+ * - When `output.footer` is a function and `node` is absent, returns `undefined`.
2222
+ * - When `output.footer` is a string, returns it directly.
2223
+ * - Otherwise returns `undefined`.
2224
+ *
2225
+ * @example String footer
2226
+ * ```ts
2227
+ * defaultResolveFooter(undefined, { output: { footer: '// end of file' }, config })
2228
+ * // → '// end of file'
2229
+ * ```
2230
+ *
2231
+ * @example Function footer with node
2232
+ * ```ts
2233
+ * defaultResolveFooter(rootNode, { output: { footer: (node) => `// ${node.title}` }, config })
2234
+ * // → '// Pet Store'
2235
+ * ```
2236
+ */
2237
+ function defaultResolveFooter(node, { output }) {
2238
+ if (typeof output?.footer === "function") return node ? output.footer(node) : void 0;
2239
+ if (typeof output?.footer === "string") return output.footer;
2240
+ }
2241
+ /**
2242
+ * Defines a resolver for a plugin, injecting built-in defaults for name casing,
2243
+ * include/exclude/override filtering, path resolution, and file construction.
2244
+ *
2245
+ * All four defaults can be overridden by providing them in the builder function:
2246
+ * - `default` — name casing strategy (camelCase / PascalCase)
2247
+ * - `resolveOptions` — include/exclude/override filtering
2248
+ * - `resolvePath` — output path computation
2249
+ * - `resolveFile` — full `KubbFile.File` construction
2250
+ *
2251
+ * Methods in the builder have access to `this` (the full resolver object), so they
2252
+ * can call other resolver methods without circular imports.
2253
+ *
2254
+ * @example Basic resolver with naming helpers
2255
+ * ```ts
2256
+ * export const resolver = defineResolver<PluginTs>(() => ({
2257
+ * name: 'default',
2258
+ * resolveName(node) {
2259
+ * return this.default(node.name, 'function')
2260
+ * },
2261
+ * resolveTypedName(node) {
2262
+ * return this.default(node.name, 'type')
2263
+ * },
2264
+ * }))
2265
+ * ```
2266
+ *
2267
+ * @example Override resolvePath for a custom output structure
2268
+ * ```ts
2269
+ * export const resolver = defineResolver<PluginTs>(() => ({
2270
+ * name: 'custom',
2271
+ * resolvePath({ baseName }, { root, output }) {
2272
+ * return path.resolve(root, output.path, 'generated', baseName)
2273
+ * },
2274
+ * }))
2275
+ * ```
2276
+ *
2277
+ * @example Use this.default inside a helper
2278
+ * ```ts
2279
+ * export const resolver = defineResolver<PluginTs>(() => ({
2280
+ * name: 'default',
2281
+ * resolveParamName(node, param) {
2282
+ * return this.default(`${node.operationId} ${param.in} ${param.name}`, 'type')
2283
+ * },
2284
+ * }))
2285
+ * ```
2286
+ */
2287
+ function defineResolver(build) {
2288
+ return {
2289
+ default: defaultResolver,
2290
+ resolveOptions: defaultResolveOptions,
2291
+ resolvePath: defaultResolvePath,
2292
+ resolveFile: defaultResolveFile,
2293
+ resolveBanner: defaultResolveBanner,
2294
+ resolveFooter: defaultResolveFooter,
2295
+ ...build()
2296
+ };
2297
+ }
2298
+ //#endregion
2299
+ //#region src/renderNode.tsx
2300
+ /**
2301
+ * Renders a React component for a list of operation nodes (V2 generators).
2302
+ */
2303
+ async function renderOperations(nodes, options) {
2304
+ const { config, fabric, plugin, Component, driver, adapter } = options;
2305
+ if (!Component) return;
2306
+ const fabricChild = (0, _kubb_react_fabric.createReactFabric)();
2307
+ await fabricChild.render(/* @__PURE__ */ (0, _kubb_react_fabric_jsx_runtime.jsx)(_kubb_react_fabric.Fabric, {
2308
+ meta: {
2309
+ plugin,
2310
+ driver
2311
+ },
2312
+ children: /* @__PURE__ */ (0, _kubb_react_fabric_jsx_runtime.jsx)(Component, {
2313
+ config,
2314
+ plugin,
2315
+ adapter,
2316
+ nodes,
2317
+ options: options.options
2318
+ })
2319
+ }));
2320
+ fabric.context.fileManager.upsert(...fabricChild.files);
2321
+ fabricChild.unmount();
2322
+ }
2323
+ /**
2324
+ * Renders a React component for a single operation node (V2 generators).
2325
+ */
2326
+ async function renderOperation(node, options) {
2327
+ const { config, fabric, plugin, Component, adapter, driver } = options;
2328
+ if (!Component) return;
2329
+ const fabricChild = (0, _kubb_react_fabric.createReactFabric)();
2330
+ await fabricChild.render(/* @__PURE__ */ (0, _kubb_react_fabric_jsx_runtime.jsx)(_kubb_react_fabric.Fabric, {
2331
+ meta: {
2332
+ plugin,
2333
+ driver
2334
+ },
2335
+ children: /* @__PURE__ */ (0, _kubb_react_fabric_jsx_runtime.jsx)(Component, {
2336
+ config,
2337
+ plugin,
2338
+ adapter,
2339
+ node,
2340
+ options: options.options
2341
+ })
2342
+ }));
2343
+ fabric.context.fileManager.upsert(...fabricChild.files);
2344
+ fabricChild.unmount();
2345
+ }
2346
+ /**
2347
+ * Renders a React component for a single schema node (V2 generators).
2348
+ */
2349
+ async function renderSchema(node, options) {
2350
+ const { config, fabric, plugin, Component, adapter, driver } = options;
2351
+ if (!Component) return;
2352
+ const fabricChild = (0, _kubb_react_fabric.createReactFabric)();
2353
+ await fabricChild.render(/* @__PURE__ */ (0, _kubb_react_fabric_jsx_runtime.jsx)(_kubb_react_fabric.Fabric, {
2354
+ meta: {
2355
+ plugin,
2356
+ driver
2357
+ },
2358
+ children: /* @__PURE__ */ (0, _kubb_react_fabric_jsx_runtime.jsx)(Component, {
2359
+ config,
2360
+ plugin,
2361
+ adapter,
2362
+ node,
2363
+ options: options.options
2364
+ })
2365
+ }));
2366
+ fabric.context.fileManager.upsert(...fabricChild.files);
2367
+ fabricChild.unmount();
2368
+ }
1886
2369
  //#endregion
1887
2370
  //#region src/storages/memoryStorage.ts
1888
2371
  /**
@@ -1902,7 +2385,7 @@ var PackageManager = class PackageManager {
1902
2385
  * })
1903
2386
  * ```
1904
2387
  */
1905
- const memoryStorage = defineStorage(() => {
2388
+ const memoryStorage = createStorage(() => {
1906
2389
  const store = /* @__PURE__ */ new Map();
1907
2390
  return {
1908
2391
  name: "memory",
@@ -1934,7 +2417,7 @@ const memoryStorage = defineStorage(() => {
1934
2417
  //#endregion
1935
2418
  //#region src/utils/FunctionParams.ts
1936
2419
  /**
1937
- * @deprecated
2420
+ * @deprecated use ast package instead
1938
2421
  */
1939
2422
  var FunctionParams = class FunctionParams {
1940
2423
  #items = [];
@@ -2010,14 +2493,10 @@ var FunctionParams = class FunctionParams {
2010
2493
  //#endregion
2011
2494
  //#region src/utils/formatters.ts
2012
2495
  /**
2013
- * Check if a formatter command is available in the system.
2014
- *
2015
- * @param formatter - The formatter to check ('biome', 'prettier', or 'oxfmt')
2016
- * @returns Promise that resolves to true if the formatter is available, false otherwise
2496
+ * Returns `true` when the given formatter is installed and callable.
2017
2497
  *
2018
- * @remarks
2019
- * This function checks availability by running `<formatter> --version` command.
2020
- * All supported formatters (biome, prettier, oxfmt) implement the --version flag.
2498
+ * Availability is detected by running `<formatter> --version` and checking
2499
+ * that the process exits without error.
2021
2500
  */
2022
2501
  async function isFormatterAvailable(formatter) {
2023
2502
  try {
@@ -2028,34 +2507,39 @@ async function isFormatterAvailable(formatter) {
2028
2507
  }
2029
2508
  }
2030
2509
  /**
2031
- * Detect which formatter is available in the system.
2510
+ * Detects the first available code formatter on the current system.
2032
2511
  *
2033
- * @returns Promise that resolves to the first available formatter or undefined if none are found
2034
- *
2035
- * @remarks
2036
- * Checks in order of preference: biome, oxfmt, prettier.
2037
- * Uses the `--version` flag to detect if each formatter command is available.
2038
- * This is a reliable method as all supported formatters implement this flag.
2512
+ * - Checks in preference order: `biome`, `oxfmt`, `prettier`.
2513
+ * - Returns `null` when none are found.
2039
2514
  *
2040
2515
  * @example
2041
- * ```typescript
2516
+ * ```ts
2042
2517
  * const formatter = await detectFormatter()
2043
2518
  * if (formatter) {
2044
2519
  * console.log(`Using ${formatter} for formatting`)
2045
- * } else {
2046
- * console.log('No formatter found')
2047
2520
  * }
2048
2521
  * ```
2049
2522
  */
2050
2523
  async function detectFormatter() {
2051
- for (const formatter of [
2524
+ const formatterNames = new Set([
2052
2525
  "biome",
2053
2526
  "oxfmt",
2054
2527
  "prettier"
2055
- ]) if (await isFormatterAvailable(formatter)) return formatter;
2528
+ ]);
2529
+ for (const formatter of formatterNames) if (await isFormatterAvailable(formatter)) return formatter;
2530
+ return null;
2056
2531
  }
2057
2532
  //#endregion
2058
2533
  //#region src/utils/TreeNode.ts
2534
+ /**
2535
+ * Tree structure used to build per-directory barrel (`index.ts`) files from a
2536
+ * flat list of generated {@link KubbFile.File} entries.
2537
+ *
2538
+ * Each node represents either a directory or a file within the output tree.
2539
+ * Use {@link TreeNode.build} to construct a root node from a file list, then
2540
+ * traverse with {@link TreeNode.forEach}, {@link TreeNode.leaves}, or the
2541
+ * `*Deep` helpers.
2542
+ */
2059
2543
  var TreeNode = class TreeNode {
2060
2544
  data;
2061
2545
  parent;
@@ -2071,10 +2555,18 @@ var TreeNode = class TreeNode {
2071
2555
  this.children.push(child);
2072
2556
  return child;
2073
2557
  }
2558
+ /**
2559
+ * Returns the root ancestor of this node, walking up via `parent` links.
2560
+ */
2074
2561
  get root() {
2075
2562
  if (!this.parent) return this;
2076
2563
  return this.parent.root;
2077
2564
  }
2565
+ /**
2566
+ * Returns all leaf descendants (nodes with no children) of this node.
2567
+ *
2568
+ * Results are cached after the first traversal.
2569
+ */
2078
2570
  get leaves() {
2079
2571
  if (!this.children || this.children.length === 0) return [this];
2080
2572
  if (this.#cachedLeaves) return this.#cachedLeaves;
@@ -2105,6 +2597,12 @@ var TreeNode = class TreeNode {
2105
2597
  if (typeof callback !== "function") throw new TypeError("map() callback must be a function");
2106
2598
  return this.leaves.map(callback);
2107
2599
  }
2600
+ /**
2601
+ * Builds a {@link TreeNode} tree from a flat list of files.
2602
+ *
2603
+ * - Filters to files under `root` (when provided) and skips `.json` files.
2604
+ * - Returns `null` when no files match.
2605
+ */
2108
2606
  static build(files, root) {
2109
2607
  try {
2110
2608
  const filteredTree = buildDirectoryTree(files, root);
@@ -2175,65 +2673,64 @@ function buildDirectoryTree(files, rootFolder = "") {
2175
2673
  return root;
2176
2674
  }
2177
2675
  //#endregion
2178
- //#region src/BarrelManager.ts
2676
+ //#region src/utils/getBarrelFiles.ts
2179
2677
  /** biome-ignore-all lint/suspicious/useIterableCallbackReturn: not needed */
2180
- var BarrelManager = class {
2181
- getFiles({ files: generatedFiles, root }) {
2182
- const cachedFiles = /* @__PURE__ */ new Map();
2183
- TreeNode.build(generatedFiles, root)?.forEach((treeNode) => {
2184
- if (!treeNode || !treeNode.children || !treeNode.parent?.data.path) return;
2185
- const barrelFile = {
2186
- path: (0, node_path.join)(treeNode.parent?.data.path, "index.ts"),
2187
- baseName: "index.ts",
2188
- exports: [],
2189
- imports: [],
2190
- sources: []
2191
- };
2192
- const previousBarrelFile = cachedFiles.get(barrelFile.path);
2193
- treeNode.leaves.forEach((item) => {
2194
- if (!item.data.name) return;
2195
- (item.data.file?.sources || []).forEach((source) => {
2196
- if (!item.data.file?.path || !source.isIndexable || !source.name) return;
2197
- if (previousBarrelFile?.sources.some((item) => item.name === source.name && item.isTypeOnly === source.isTypeOnly)) return;
2198
- barrelFile.exports.push({
2199
- name: [source.name],
2200
- path: getRelativePath(treeNode.parent?.data.path, item.data.path),
2201
- isTypeOnly: source.isTypeOnly
2202
- });
2203
- barrelFile.sources.push({
2204
- name: source.name,
2205
- isTypeOnly: source.isTypeOnly,
2206
- value: "",
2207
- isExportable: false,
2208
- isIndexable: false
2209
- });
2678
+ function getBarrelFilesByRoot(root, files) {
2679
+ const cachedFiles = /* @__PURE__ */ new Map();
2680
+ TreeNode.build(files, root)?.forEach((treeNode) => {
2681
+ if (!treeNode?.children || !treeNode.parent?.data.path) return;
2682
+ const barrelFile = {
2683
+ path: (0, node_path.join)(treeNode.parent?.data.path, "index.ts"),
2684
+ baseName: "index.ts",
2685
+ exports: [],
2686
+ imports: [],
2687
+ sources: []
2688
+ };
2689
+ const previousBarrelFile = cachedFiles.get(barrelFile.path);
2690
+ treeNode.leaves.forEach((item) => {
2691
+ if (!item.data.name) return;
2692
+ (item.data.file?.sources || []).forEach((source) => {
2693
+ if (!item.data.file?.path || !source.isIndexable || !source.name) return;
2694
+ if (previousBarrelFile?.sources.some((item) => item.name === source.name && item.isTypeOnly === source.isTypeOnly)) return;
2695
+ barrelFile.exports.push({
2696
+ name: [source.name],
2697
+ path: getRelativePath(treeNode.parent?.data.path, item.data.path),
2698
+ isTypeOnly: source.isTypeOnly
2699
+ });
2700
+ barrelFile.sources.push({
2701
+ name: source.name,
2702
+ isTypeOnly: source.isTypeOnly,
2703
+ value: "",
2704
+ isExportable: false,
2705
+ isIndexable: false
2210
2706
  });
2211
2707
  });
2212
- if (previousBarrelFile) {
2213
- previousBarrelFile.sources.push(...barrelFile.sources);
2214
- previousBarrelFile.exports?.push(...barrelFile.exports || []);
2215
- } else cachedFiles.set(barrelFile.path, barrelFile);
2216
2708
  });
2217
- return [...cachedFiles.values()];
2218
- }
2219
- };
2220
- //#endregion
2221
- //#region src/utils/getBarrelFiles.ts
2709
+ if (previousBarrelFile) {
2710
+ previousBarrelFile.sources.push(...barrelFile.sources);
2711
+ previousBarrelFile.exports?.push(...barrelFile.exports || []);
2712
+ } else cachedFiles.set(barrelFile.path, barrelFile);
2713
+ });
2714
+ return [...cachedFiles.values()];
2715
+ }
2222
2716
  function trimExtName(text) {
2223
2717
  const dotIndex = text.lastIndexOf(".");
2224
2718
  if (dotIndex > 0 && !text.includes("/", dotIndex)) return text.slice(0, dotIndex);
2225
2719
  return text;
2226
2720
  }
2721
+ /**
2722
+ * Generates `index.ts` barrel files for all directories under `root/output.path`.
2723
+ *
2724
+ * - Returns an empty array when `type` is falsy or `'propagate'`.
2725
+ * - Skips generation when the output path itself ends with `index` (already a barrel).
2726
+ * - When `type` is `'all'`, strips named exports so every re-export becomes a wildcard (`export * from`).
2727
+ * - Attaches `meta` to each barrel file for downstream plugin identification.
2728
+ */
2227
2729
  async function getBarrelFiles(files, { type, meta = {}, root, output }) {
2228
2730
  if (!type || type === "propagate") return [];
2229
- const barrelManager = new BarrelManager();
2230
2731
  const pathToBuildFrom = (0, node_path.join)(root, output.path);
2231
2732
  if (trimExtName(pathToBuildFrom).endsWith("index")) return [];
2232
- const barrelFiles = barrelManager.getFiles({
2233
- files,
2234
- root: pathToBuildFrom,
2235
- meta
2236
- });
2733
+ const barrelFiles = getBarrelFilesByRoot(pathToBuildFrom, files);
2237
2734
  if (type === "all") return barrelFiles.map((file) => {
2238
2735
  return {
2239
2736
  ...file,
@@ -2253,38 +2750,62 @@ async function getBarrelFiles(files, { type, meta = {}, root, output }) {
2253
2750
  });
2254
2751
  }
2255
2752
  //#endregion
2256
- //#region src/utils/getPlugins.ts
2257
- function isJSONPlugins(plugins) {
2258
- return Array.isArray(plugins) && plugins.some((plugin) => Array.isArray(plugin) && typeof plugin[0] === "string");
2259
- }
2260
- function isObjectPlugins(plugins) {
2261
- return plugins instanceof Object && !Array.isArray(plugins);
2753
+ //#region src/utils/getConfigs.ts
2754
+ /**
2755
+ * Resolves a {@link ConfigInput} into a normalized array of {@link Config} objects.
2756
+ *
2757
+ * - Awaits the config when it is a `Promise`.
2758
+ * - Calls the factory function with `args` when the config is a function.
2759
+ * - Wraps a single config object in an array for uniform downstream handling.
2760
+ */
2761
+ async function getConfigs(config, args) {
2762
+ const resolved = await (typeof config === "function" ? config(args) : config);
2763
+ return (Array.isArray(resolved) ? resolved : [resolved]).map((item) => ({ ...item }));
2262
2764
  }
2263
- function getPlugins(plugins) {
2264
- if (isObjectPlugins(plugins)) throw new Error("Object plugins are not supported anymore, best to use http://kubb.dev/getting-started/configure#json");
2265
- if (isJSONPlugins(plugins)) throw new Error("JSON plugins are not supported anymore, best to use http://kubb.dev/getting-started/configure#json");
2266
- return Promise.resolve(plugins);
2765
+ //#endregion
2766
+ //#region src/utils/mergeResolvers.ts
2767
+ /**
2768
+ * Merges an array of resolvers into a single resolver. Later entries override earlier ones (last wins).
2769
+ */
2770
+ function mergeResolvers(...resolvers) {
2771
+ return resolvers.reduce((acc, curr) => ({
2772
+ ...acc,
2773
+ ...curr
2774
+ }), resolvers[0]);
2267
2775
  }
2268
2776
  //#endregion
2269
- //#region src/utils/getConfigs.ts
2777
+ //#region src/utils/getPreset.ts
2270
2778
  /**
2271
- * Converting UserConfig to Config Array without a change in the object beside the JSON convert.
2779
+ * Resolves a named preset into merged resolvers, transformers, and generators.
2780
+ *
2781
+ * - Merges the preset's resolvers on top of the first (default)
2782
+ * - Merges any additional user-supplied resolvers on top of that to produce the final `resolver`.
2783
+ * - Concatenates preset transformers before user-supplied transformers.
2784
+ * - Combines preset generators with user-supplied generators; falls back to the `default` preset's generators when neither provides any.
2272
2785
  */
2273
- async function getConfigs(config, args) {
2274
- let userConfigs = await (typeof config === "function" ? Promise.resolve(config(args)) : Promise.resolve(config));
2275
- if (!Array.isArray(userConfigs)) userConfigs = [userConfigs];
2276
- const results = [];
2277
- for (const item of userConfigs) {
2278
- const plugins = item.plugins ? await getPlugins(item.plugins) : void 0;
2279
- results.push({
2280
- ...item,
2281
- plugins
2282
- });
2283
- }
2284
- return results;
2786
+ function getPreset(params) {
2787
+ const { preset: presetName, presets, resolvers, transformers: userTransformers, generators: userGenerators } = params;
2788
+ const [defaultResolver, ...userResolvers] = resolvers;
2789
+ const preset = presets[presetName];
2790
+ const resolver = mergeResolvers(mergeResolvers(defaultResolver, ...preset?.resolvers ?? []), ...userResolvers ?? []);
2791
+ const transformers = [...preset?.transformers ?? [], ...userTransformers ?? []];
2792
+ const presetGenerators = preset?.generators ?? [];
2793
+ const defaultPresetGenerators = presets["default"]?.generators ?? [];
2794
+ return {
2795
+ resolver,
2796
+ transformers,
2797
+ generators: presetGenerators.length > 0 || userGenerators.length ? [...presetGenerators, ...userGenerators] : defaultPresetGenerators,
2798
+ preset
2799
+ };
2285
2800
  }
2286
2801
  //#endregion
2287
2802
  //#region src/utils/linters.ts
2803
+ /**
2804
+ * Returns `true` when the given linter is installed and callable.
2805
+ *
2806
+ * Availability is detected by running `<linter> --version` and checking
2807
+ * that the process exits without error.
2808
+ */
2288
2809
  async function isLinterAvailable(linter) {
2289
2810
  try {
2290
2811
  await (0, tinyexec.x)(linter, ["--version"], { nodeOptions: { stdio: "ignore" } });
@@ -2293,33 +2814,101 @@ async function isLinterAvailable(linter) {
2293
2814
  return false;
2294
2815
  }
2295
2816
  }
2817
+ /**
2818
+ * Detects the first available linter on the current system.
2819
+ *
2820
+ * - Checks in preference order: `biome`, `oxlint`, `eslint`.
2821
+ * - Returns `null` when none are found.
2822
+ *
2823
+ * @example
2824
+ * ```ts
2825
+ * const linter = await detectLinter()
2826
+ * if (linter) {
2827
+ * console.log(`Using ${linter} for linting`)
2828
+ * }
2829
+ * ```
2830
+ */
2296
2831
  async function detectLinter() {
2297
- for (const linter of [
2832
+ const linterNames = new Set([
2298
2833
  "biome",
2299
2834
  "oxlint",
2300
2835
  "eslint"
2301
- ]) if (await isLinterAvailable(linter)) return linter;
2836
+ ]);
2837
+ for (const linter of linterNames) if (await isLinterAvailable(linter)) return linter;
2838
+ return null;
2839
+ }
2840
+ //#endregion
2841
+ //#region src/utils/packageJSON.ts
2842
+ function getPackageJSONSync(cwd) {
2843
+ const pkgPath = empathic_package.up({ cwd });
2844
+ if (!pkgPath) return null;
2845
+ return JSON.parse(readSync(pkgPath));
2846
+ }
2847
+ function match(packageJSON, dependency) {
2848
+ const dependencies = {
2849
+ ...packageJSON.dependencies || {},
2850
+ ...packageJSON.devDependencies || {}
2851
+ };
2852
+ if (typeof dependency === "string" && dependencies[dependency]) return dependencies[dependency];
2853
+ const matched = Object.keys(dependencies).find((dep) => dep.match(dependency));
2854
+ return matched ? dependencies[matched] ?? null : null;
2855
+ }
2856
+ function getVersionSync(dependency, cwd) {
2857
+ const packageJSON = getPackageJSONSync(cwd);
2858
+ return packageJSON ? match(packageJSON, dependency) : null;
2859
+ }
2860
+ /**
2861
+ * Returns `true` when the nearest `package.json` declares a dependency that
2862
+ * satisfies the given semver range.
2863
+ *
2864
+ * - Searches both `dependencies` and `devDependencies`.
2865
+ * - Accepts a string package name or a `RegExp` to match scoped/pattern packages.
2866
+ * - Uses `semver.satisfies` for range comparison; returns `false` when the
2867
+ * version string cannot be coerced into a valid semver.
2868
+ *
2869
+ * @example
2870
+ * ```ts
2871
+ * satisfiesDependency('react', '>=18') // true when react@18.x is installed
2872
+ * satisfiesDependency(/^@tanstack\//, '>=5') // true when any @tanstack/* >=5 is found
2873
+ * ```
2874
+ */
2875
+ function satisfiesDependency(dependency, version, cwd) {
2876
+ const packageVersion = getVersionSync(dependency, cwd);
2877
+ if (!packageVersion) return false;
2878
+ if (packageVersion === version) return true;
2879
+ const semVer = (0, semver.coerce)(packageVersion);
2880
+ if (!semVer) return false;
2881
+ return (0, semver.satisfies)(semVer, version);
2302
2882
  }
2303
2883
  //#endregion
2304
2884
  exports.AsyncEventEmitter = AsyncEventEmitter;
2305
2885
  exports.FunctionParams = FunctionParams;
2306
- exports.PackageManager = PackageManager;
2307
- exports.PluginManager = PluginManager;
2308
- exports.PromiseManager = PromiseManager;
2886
+ exports.PluginDriver = PluginDriver;
2309
2887
  exports.URLPath = URLPath;
2310
2888
  exports.build = build;
2889
+ exports.buildDefaultBanner = buildDefaultBanner;
2890
+ exports.createAdapter = createAdapter;
2891
+ exports.createPlugin = createPlugin;
2892
+ exports.createStorage = createStorage;
2311
2893
  exports.default = build;
2312
- exports.defineAdapter = defineAdapter;
2894
+ exports.defaultResolveBanner = defaultResolveBanner;
2895
+ exports.defaultResolveFile = defaultResolveFile;
2896
+ exports.defaultResolveFooter = defaultResolveFooter;
2897
+ exports.defaultResolveOptions = defaultResolveOptions;
2898
+ exports.defaultResolvePath = defaultResolvePath;
2899
+ exports.defineBuilder = defineBuilder;
2313
2900
  exports.defineConfig = defineConfig;
2901
+ exports.defineGenerator = defineGenerator;
2314
2902
  exports.defineLogger = defineLogger;
2315
- exports.definePlugin = definePlugin;
2903
+ exports.definePreset = definePreset;
2904
+ exports.definePresets = definePresets;
2316
2905
  Object.defineProperty(exports, "definePrinter", {
2317
2906
  enumerable: true,
2318
2907
  get: function() {
2319
2908
  return _kubb_ast.definePrinter;
2320
2909
  }
2321
2910
  });
2322
- exports.defineStorage = defineStorage;
2911
+ exports.defineResolver = defineResolver;
2323
2912
  exports.detectFormatter = detectFormatter;
2324
2913
  exports.detectLinter = detectLinter;
2325
2914
  exports.formatters = formatters;
@@ -2327,11 +2916,17 @@ exports.fsStorage = fsStorage;
2327
2916
  exports.getBarrelFiles = getBarrelFiles;
2328
2917
  exports.getConfigs = getConfigs;
2329
2918
  exports.getMode = getMode;
2919
+ exports.getPreset = getPreset;
2330
2920
  exports.isInputPath = isInputPath;
2331
2921
  exports.linters = linters;
2332
2922
  exports.logLevel = logLevel;
2333
2923
  exports.memoryStorage = memoryStorage;
2924
+ exports.mergeResolvers = mergeResolvers;
2925
+ exports.renderOperation = renderOperation;
2926
+ exports.renderOperations = renderOperations;
2927
+ exports.renderSchema = renderSchema;
2334
2928
  exports.safeBuild = safeBuild;
2929
+ exports.satisfiesDependency = satisfiesDependency;
2335
2930
  exports.setup = setup;
2336
2931
 
2337
2932
  //# sourceMappingURL=index.cjs.map