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