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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. package/dist/PluginDriver-D0dY_hpJ.d.ts +1986 -0
  2. package/dist/{chunk-ByKO4r7w.cjs → chunk-MlS0t1Af.cjs} +15 -0
  3. package/dist/chunk-O_arW02_.js +17 -0
  4. package/dist/hooks.cjs +13 -28
  5. package/dist/hooks.cjs.map +1 -1
  6. package/dist/hooks.d.ts +11 -37
  7. package/dist/hooks.js +14 -28
  8. package/dist/hooks.js.map +1 -1
  9. package/dist/index.cjs +1469 -831
  10. package/dist/index.cjs.map +1 -1
  11. package/dist/index.d.ts +572 -191
  12. package/dist/index.js +1443 -826
  13. package/dist/index.js.map +1 -1
  14. package/package.json +7 -7
  15. package/src/Kubb.ts +38 -56
  16. package/src/KubbFile.ts +143 -0
  17. package/src/{PluginManager.ts → PluginDriver.ts} +159 -170
  18. package/src/build.ts +213 -65
  19. package/src/constants.ts +39 -6
  20. package/src/createAdapter.ts +25 -0
  21. package/src/createPlugin.ts +30 -0
  22. package/src/createStorage.ts +58 -0
  23. package/src/{config.ts → defineConfig.ts} +11 -16
  24. package/src/defineGenerator.ts +126 -0
  25. package/src/defineLogger.ts +13 -3
  26. package/src/defineParser.ts +57 -0
  27. package/src/definePresets.ts +16 -0
  28. package/src/defineResolver.ts +454 -0
  29. package/src/hooks/index.ts +1 -6
  30. package/src/hooks/useDriver.ts +11 -0
  31. package/src/hooks/useMode.ts +4 -4
  32. package/src/hooks/usePlugin.ts +3 -3
  33. package/src/index.ts +22 -10
  34. package/src/renderNode.tsx +25 -0
  35. package/src/storages/fsStorage.ts +2 -2
  36. package/src/storages/memoryStorage.ts +2 -2
  37. package/src/types.ts +639 -52
  38. package/src/utils/FunctionParams.ts +2 -2
  39. package/src/utils/TreeNode.ts +40 -2
  40. package/src/utils/diagnostics.ts +4 -1
  41. package/src/utils/executeStrategies.ts +29 -10
  42. package/src/utils/formatters.ts +10 -21
  43. package/src/utils/getBarrelFiles.ts +80 -10
  44. package/src/utils/getConfigs.ts +9 -23
  45. package/src/utils/getPreset.ts +78 -0
  46. package/src/utils/isInputPath.ts +8 -0
  47. package/src/utils/linters.ts +23 -3
  48. package/src/utils/packageJSON.ts +76 -0
  49. package/dist/chunk--u3MIqq1.js +0 -8
  50. package/dist/types-CiPWLv-5.d.ts +0 -1001
  51. package/src/BarrelManager.ts +0 -74
  52. package/src/PackageManager.ts +0 -180
  53. package/src/PromiseManager.ts +0 -40
  54. package/src/defineAdapter.ts +0 -22
  55. package/src/definePlugin.ts +0 -12
  56. package/src/defineStorage.ts +0 -56
  57. package/src/errors.ts +0 -1
  58. package/src/hooks/useKubb.ts +0 -22
  59. package/src/hooks/usePluginManager.ts +0 -11
  60. package/src/utils/getPlugins.ts +0 -23
package/dist/index.js CHANGED
@@ -1,29 +1,28 @@
1
- import "./chunk--u3MIqq1.js";
2
- import mod from "node:module";
1
+ import { t as __exportAll } from "./chunk-O_arW02_.js";
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
5
  import path, { basename, dirname, extname, join, posix, relative, resolve } from "node:path";
8
- import { definePrinter } from "@kubb/ast";
9
- import { createFabric } from "@kubb/react-fabric";
10
- import { typescriptParser } from "@kubb/react-fabric/parsers";
6
+ import { composeTransformers, composeTransformers as composeTransformers$1, definePrinter, isOperationNode, isSchemaNode, transform, walk } from "@kubb/ast";
7
+ import { Fabric, createFabric, createReactFabric } from "@kubb/react-fabric";
11
8
  import { fsPlugin } from "@kubb/react-fabric/plugins";
12
9
  import { performance } from "node:perf_hooks";
13
10
  import { deflateSync } from "fflate";
14
11
  import { x } from "tinyexec";
12
+ import { jsx } from "@kubb/react-fabric/jsx-runtime";
15
13
  import { version } from "node:process";
16
- import os from "node:os";
17
- import { pathToFileURL } from "node:url";
14
+ import { sortBy } from "remeda";
18
15
  import * as pkg from "empathic/package";
19
16
  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. */
23
- var ValidationPluginError = class extends Error {};
17
+ //#region ../../internals/utils/src/errors.ts
24
18
  /**
25
19
  * Thrown when one or more errors occur during a Kubb build.
26
20
  * Carries the full list of underlying errors on `errors`.
21
+ *
22
+ * @example
23
+ * ```ts
24
+ * throw new BuildError('Build failed', { errors: [err1, err2] })
25
+ * ```
27
26
  */
28
27
  var BuildError = class extends Error {
29
28
  errors;
@@ -35,19 +34,34 @@ var BuildError = class extends Error {
35
34
  };
36
35
  /**
37
36
  * 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)`.
37
+ * Returns the value as-is when it is already an `Error`; otherwise wraps it with `String(value)`.
38
+ *
39
+ * @example
40
+ * ```ts
41
+ * try { ... } catch(err) {
42
+ * throw new BuildError('Build failed', { cause: toError(err), errors: [] })
43
+ * }
44
+ * ```
40
45
  */
41
46
  function toError(value) {
42
47
  return value instanceof Error ? value : new Error(String(value));
43
48
  }
49
+ //#endregion
50
+ //#region ../../internals/utils/src/asyncEventEmitter.ts
44
51
  /**
45
- * A typed EventEmitter that awaits all async listeners before resolving.
52
+ * Typed `EventEmitter` that awaits all async listeners before resolving.
46
53
  * Wraps Node's `EventEmitter` with full TypeScript event-map inference.
54
+ *
55
+ * @example
56
+ * ```ts
57
+ * const emitter = new AsyncEventEmitter<{ build: [name: string] }>()
58
+ * emitter.on('build', async (name) => { console.log(name) })
59
+ * await emitter.emit('build', 'petstore') // all listeners awaited
60
+ * ```
47
61
  */
48
62
  var AsyncEventEmitter = class {
49
63
  /**
50
- * `maxListener` controls the maximum number of listeners per event before Node emits a memory-leak warning.
64
+ * Maximum number of listeners per event before Node emits a memory-leak warning.
51
65
  * @default 10
52
66
  */
53
67
  constructor(maxListener = 10) {
@@ -55,31 +69,48 @@ var AsyncEventEmitter = class {
55
69
  }
56
70
  #emitter = new EventEmitter();
57
71
  /**
58
- * Emits an event and awaits all registered listeners in parallel.
72
+ * Emits `eventName` and awaits all registered listeners sequentially.
59
73
  * Throws if any listener rejects, wrapping the cause with the event name and serialized arguments.
74
+ *
75
+ * @example
76
+ * ```ts
77
+ * await emitter.emit('build', 'petstore')
78
+ * ```
60
79
  */
61
80
  async emit(eventName, ...eventArgs) {
62
81
  const listeners = this.#emitter.listeners(eventName);
63
82
  if (listeners.length === 0) return;
64
- await Promise.all(listeners.map(async (listener) => {
83
+ for (const listener of listeners) try {
84
+ await listener(...eventArgs);
85
+ } catch (err) {
86
+ let serializedArgs;
65
87
  try {
66
- return await listener(...eventArgs);
67
- } catch (err) {
68
- let serializedArgs;
69
- try {
70
- serializedArgs = JSON.stringify(eventArgs);
71
- } catch {
72
- serializedArgs = String(eventArgs);
73
- }
74
- throw new Error(`Error in async listener for "${eventName}" with eventArgs ${serializedArgs}`, { cause: toError(err) });
88
+ serializedArgs = JSON.stringify(eventArgs);
89
+ } catch {
90
+ serializedArgs = String(eventArgs);
75
91
  }
76
- }));
92
+ throw new Error(`Error in async listener for "${eventName}" with eventArgs ${serializedArgs}`, { cause: toError(err) });
93
+ }
77
94
  }
78
- /** Registers a persistent listener for the given event. */
95
+ /**
96
+ * Registers a persistent listener for `eventName`.
97
+ *
98
+ * @example
99
+ * ```ts
100
+ * emitter.on('build', async (name) => { console.log(name) })
101
+ * ```
102
+ */
79
103
  on(eventName, handler) {
80
104
  this.#emitter.on(eventName, handler);
81
105
  }
82
- /** Registers a one-shot listener that removes itself after the first invocation. */
106
+ /**
107
+ * Registers a one-shot listener that removes itself after the first invocation.
108
+ *
109
+ * @example
110
+ * ```ts
111
+ * emitter.onOnce('build', async (name) => { console.log(name) })
112
+ * ```
113
+ */
83
114
  onOnce(eventName, handler) {
84
115
  const wrapper = (...args) => {
85
116
  this.off(eventName, wrapper);
@@ -87,15 +118,31 @@ var AsyncEventEmitter = class {
87
118
  };
88
119
  this.on(eventName, wrapper);
89
120
  }
90
- /** Removes a previously registered listener. */
121
+ /**
122
+ * Removes a previously registered listener.
123
+ *
124
+ * @example
125
+ * ```ts
126
+ * emitter.off('build', handler)
127
+ * ```
128
+ */
91
129
  off(eventName, handler) {
92
130
  this.#emitter.off(eventName, handler);
93
131
  }
94
- /** Removes all listeners from every event channel. */
132
+ /**
133
+ * Removes all listeners from every event channel.
134
+ *
135
+ * @example
136
+ * ```ts
137
+ * emitter.removeAll()
138
+ * ```
139
+ */
95
140
  removeAll() {
96
141
  this.#emitter.removeAllListeners();
97
142
  }
98
143
  };
144
+ //#endregion
145
+ //#region ../../internals/utils/src/casing.ts
99
146
  /**
100
147
  * Shared implementation for camelCase and PascalCase conversion.
101
148
  * Splits on common word boundaries (spaces, hyphens, underscores, dots, slashes, colons)
@@ -114,9 +161,12 @@ function toCamelOrPascal(text, pascal) {
114
161
  * Splits `text` on `.` and applies `transformPart` to each segment.
115
162
  * The last segment receives `isLast = true`, all earlier segments receive `false`.
116
163
  * Segments are joined with `/` to form a file path.
164
+ *
165
+ * Only splits on dots followed by a letter so that version numbers
166
+ * embedded in operationIds (e.g. `v2025.0`) are kept intact.
117
167
  */
118
168
  function applyToFileParts(text, transformPart) {
119
- const parts = text.split(".");
169
+ const parts = text.split(/\.(?=[a-zA-Z])/);
120
170
  return parts.map((part, i) => transformPart(part, i === parts.length - 1)).join("/");
121
171
  }
122
172
  /**
@@ -134,190 +184,33 @@ function camelCase(text, { isFile, prefix = "", suffix = "" } = {}) {
134
184
  } : {}));
135
185
  return toCamelOrPascal(`${prefix} ${text} ${suffix}`, false);
136
186
  }
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
187
  /**
142
- * Serializes `CommandDefinition[]` to a plain, JSON-serializable structure.
143
- * Use to expose CLI capabilities to AI agents or MCP tools.
188
+ * Converts `text` to PascalCase.
189
+ * When `isFile` is `true`, the last dot-separated segment is PascalCased and earlier segments are camelCased.
190
+ *
191
+ * @example
192
+ * pascalCase('hello-world') // 'HelloWorld'
193
+ * pascalCase('pet.petId', { isFile: true }) // 'pet/PetId'
144
194
  */
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;
195
+ function pascalCase(text, { isFile, prefix = "", suffix = "" } = {}) {
196
+ if (isFile) return applyToFileParts(text, (part, isLast) => isLast ? pascalCase(part, {
197
+ prefix,
198
+ suffix
199
+ }) : camelCase(part));
200
+ return toCamelOrPascal(`${prefix} ${text} ${suffix}`, true);
209
201
  }
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
- });
202
+ //#endregion
203
+ //#region ../../internals/utils/src/time.ts
318
204
  /**
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.
205
+ * Calculates elapsed time in milliseconds from a high-resolution `process.hrtime` start time.
206
+ * Rounds to 2 decimal places for sub-millisecond precision without noise.
207
+ *
208
+ * @example
209
+ * ```ts
210
+ * const start = process.hrtime()
211
+ * doWork()
212
+ * getElapsedMs(start) // 42.35
213
+ * ```
321
214
  */
322
215
  function getElapsedMs(hrStart) {
323
216
  const [seconds, nanoseconds] = process.hrtime(hrStart);
@@ -325,39 +218,22 @@ function getElapsedMs(hrStart) {
325
218
  return Math.round(ms * 100) / 100;
326
219
  }
327
220
  /**
328
- * Converts a millisecond duration into a human-readable string.
329
- * Adjusts units (ms, s, m s) based on the magnitude of the duration.
221
+ * Converts a millisecond duration into a human-readable string (`ms`, `s`, or `m s`).
222
+ *
223
+ * @example
224
+ * ```ts
225
+ * formatMs(250) // '250ms'
226
+ * formatMs(1500) // '1.50s'
227
+ * formatMs(90000) // '1m 30.0s'
228
+ * ```
330
229
  */
331
230
  function formatMs(ms) {
332
231
  if (ms >= 6e4) return `${Math.floor(ms / 6e4)}m ${(ms % 6e4 / 1e3).toFixed(1)}s`;
333
232
  if (ms >= 1e3) return `${(ms / 1e3).toFixed(2)}s`;
334
233
  return `${Math.round(ms)}ms`;
335
234
  }
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");
235
+ //#endregion
236
+ //#region ../../internals/utils/src/fs.ts
361
237
  /**
362
238
  * Converts all backslashes to forward slashes.
363
239
  * Extended-length Windows paths (`\\?\...`) are left unchanged.
@@ -367,8 +243,14 @@ function toSlash(p) {
367
243
  return p.replaceAll("\\", "/");
368
244
  }
369
245
  /**
370
- * Returns the relative path from `rootDir` to `filePath`, always using
371
- * forward slashes and prefixed with `./` when not already traversing upward.
246
+ * Returns the relative path from `rootDir` to `filePath`, always using forward slashes
247
+ * and prefixed with `./` when not already traversing upward.
248
+ *
249
+ * @example
250
+ * ```ts
251
+ * getRelativePath('/src/components', '/src/components/Button.tsx') // './Button.tsx'
252
+ * getRelativePath('/src/components', '/src/utils/helpers.ts') // '../utils/helpers.ts'
253
+ * ```
372
254
  */
373
255
  function getRelativePath(rootDir, filePath) {
374
256
  if (!rootDir || !filePath) throw new Error(`Root and file should be filled in when retrieving the relativePath, ${rootDir || ""} ${filePath || ""}`);
@@ -378,43 +260,54 @@ function getRelativePath(rootDir, filePath) {
378
260
  /**
379
261
  * Resolves to `true` when the file or directory at `path` exists.
380
262
  * Uses `Bun.file().exists()` when running under Bun, `fs.access` otherwise.
263
+ *
264
+ * @example
265
+ * ```ts
266
+ * if (await exists('./kubb.config.ts')) {
267
+ * const content = await read('./kubb.config.ts')
268
+ * }
269
+ * ```
381
270
  */
382
271
  async function exists(path) {
383
272
  if (typeof Bun !== "undefined") return Bun.file(path).exists();
384
273
  return access(path).then(() => true, () => false);
385
274
  }
386
275
  /**
387
- * Reads the file at `path` as a UTF-8 string.
388
- * Uses `Bun.file().text()` when running under Bun, `fs.readFile` otherwise.
276
+ * Synchronous counterpart of `read`.
277
+ *
278
+ * @example
279
+ * ```ts
280
+ * const source = readSync('./src/Pet.ts')
281
+ * ```
389
282
  */
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
283
  function readSync(path) {
396
284
  return readFileSync(path, { encoding: "utf8" });
397
285
  }
398
286
  /**
399
287
  * 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.
288
+ * Skips the write when the trimmed content is empty or identical to what is already on disk.
402
289
  * 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.
290
+ * When `sanity` is `true`, re-reads the file after writing and throws if the content does not match.
291
+ *
292
+ * @example
293
+ * ```ts
294
+ * await write('./src/Pet.ts', source) // writes and returns trimmed content
295
+ * await write('./src/Pet.ts', source) // null — file unchanged
296
+ * await write('./src/Pet.ts', ' ') // null — empty content skipped
297
+ * ```
405
298
  */
406
299
  async function write(path, data, options = {}) {
407
300
  const trimmed = data.trim();
408
- if (trimmed === "") return void 0;
301
+ if (trimmed === "") return null;
409
302
  const resolved = resolve(path);
410
303
  if (typeof Bun !== "undefined") {
411
304
  const file = Bun.file(resolved);
412
- if ((await file.exists() ? await file.text() : null) === trimmed) return void 0;
305
+ if ((await file.exists() ? await file.text() : null) === trimmed) return null;
413
306
  await Bun.write(resolved, trimmed);
414
307
  return trimmed;
415
308
  }
416
309
  try {
417
- if (await readFile(resolved, { encoding: "utf-8" }) === trimmed) return void 0;
310
+ if (await readFile(resolved, { encoding: "utf-8" }) === trimmed) return null;
418
311
  } catch {}
419
312
  await mkdir(dirname(resolved), { recursive: true });
420
313
  await writeFile(resolved, trimmed, { encoding: "utf-8" });
@@ -425,31 +318,40 @@ async function write(path, data, options = {}) {
425
318
  }
426
319
  return trimmed;
427
320
  }
428
- /** Recursively removes `path`. Silently succeeds when `path` does not exist. */
321
+ /**
322
+ * Recursively removes `path`. Silently succeeds when `path` does not exist.
323
+ *
324
+ * @example
325
+ * ```ts
326
+ * await clean('./dist')
327
+ * ```
328
+ */
429
329
  async function clean(path) {
430
330
  return rm(path, {
431
331
  recursive: true,
432
332
  force: true
433
333
  });
434
334
  }
435
- /**
436
- * 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.
335
+ //#endregion
336
+ //#region ../../internals/utils/src/promise.ts
337
+ /** Returns `true` when `result` is a rejected `Promise.allSettled` result with a typed `reason`.
338
+ *
339
+ * @example
340
+ * ```ts
341
+ * const results = await Promise.allSettled([p1, p2])
342
+ * results.filter(isPromiseRejectedResult<Error>).map((r) => r.reason.message)
343
+ * ```
438
344
  */
439
- function setUniqueName(originalName, data) {
440
- let used = data[originalName] || 0;
441
- if (used) {
442
- data[originalName] = ++used;
443
- return originalName;
444
- }
445
- data[originalName] = 1;
446
- return originalName;
345
+ function isPromiseRejectedResult(result) {
346
+ return result.status === "rejected";
447
347
  }
348
+ //#endregion
349
+ //#region ../../internals/utils/src/reserved.ts
448
350
  /**
449
351
  * JavaScript and Java reserved words.
450
352
  * @link https://github.com/jonschlinkert/reserved/blob/master/index.js
451
353
  */
452
- const reservedWords = [
354
+ const reservedWords = new Set([
453
355
  "abstract",
454
356
  "arguments",
455
357
  "boolean",
@@ -531,18 +433,31 @@ const reservedWords = [
531
433
  "toString",
532
434
  "undefined",
533
435
  "valueOf"
534
- ];
436
+ ]);
535
437
  /**
536
- * Prefixes a word with `_` when it is a reserved JavaScript/Java identifier
537
- * or starts with a digit.
438
+ * Prefixes `word` with `_` when it is a reserved JavaScript/Java identifier or starts with a digit.
439
+ *
440
+ * @example
441
+ * ```ts
442
+ * transformReservedWord('class') // '_class'
443
+ * transformReservedWord('42foo') // '_42foo'
444
+ * transformReservedWord('status') // 'status'
445
+ * ```
538
446
  */
539
447
  function transformReservedWord(word) {
540
448
  const firstChar = word.charCodeAt(0);
541
- if (word && (reservedWords.includes(word) || firstChar >= 48 && firstChar <= 57)) return `_${word}`;
449
+ if (word && (reservedWords.has(word) || firstChar >= 48 && firstChar <= 57)) return `_${word}`;
542
450
  return word;
543
451
  }
544
452
  /**
545
453
  * Returns `true` when `name` is a syntactically valid JavaScript variable name.
454
+ *
455
+ * @example
456
+ * ```ts
457
+ * isValidVarName('status') // true
458
+ * isValidVarName('class') // false (reserved word)
459
+ * isValidVarName('42foo') // false (starts with digit)
460
+ * ```
546
461
  */
547
462
  function isValidVarName(name) {
548
463
  try {
@@ -552,6 +467,8 @@ function isValidVarName(name) {
552
467
  }
553
468
  return true;
554
469
  }
470
+ //#endregion
471
+ //#region ../../internals/utils/src/urlPath.ts
555
472
  /**
556
473
  * Parses and transforms an OpenAPI/Swagger path string into various URL formats.
557
474
  *
@@ -561,18 +478,33 @@ function isValidVarName(name) {
561
478
  * p.template // '`/pet/${petId}`'
562
479
  */
563
480
  var URLPath = class {
564
- /** The raw OpenAPI/Swagger path string, e.g. `/pet/{petId}`. */
481
+ /**
482
+ * The raw OpenAPI/Swagger path string, e.g. `/pet/{petId}`.
483
+ */
565
484
  path;
566
485
  #options;
567
486
  constructor(path, options = {}) {
568
487
  this.path = path;
569
488
  this.#options = options;
570
489
  }
571
- /** Converts the OpenAPI path to Express-style colon syntax, e.g. `/pet/{petId}` → `/pet/:petId`. */
490
+ /** Converts the OpenAPI path to Express-style colon syntax, e.g. `/pet/{petId}` → `/pet/:petId`.
491
+ *
492
+ * @example
493
+ * ```ts
494
+ * new URLPath('/pet/{petId}').URL // '/pet/:petId'
495
+ * ```
496
+ */
572
497
  get URL() {
573
498
  return this.toURLPath();
574
499
  }
575
- /** Returns `true` when `path` is a fully-qualified URL (e.g. starts with `https://`). */
500
+ /** Returns `true` when `path` is a fully-qualified URL (e.g. starts with `https://`).
501
+ *
502
+ * @example
503
+ * ```ts
504
+ * new URLPath('https://petstore.swagger.io/v2/pet').isURL // true
505
+ * new URLPath('/pet/{petId}').isURL // false
506
+ * ```
507
+ */
576
508
  get isURL() {
577
509
  try {
578
510
  return !!new URL(this.path).href;
@@ -590,11 +522,25 @@ var URLPath = class {
590
522
  get template() {
591
523
  return this.toTemplateString();
592
524
  }
593
- /** Returns the path and its extracted params as a structured `URLObject`, or as a stringified expression when `stringify` is set. */
525
+ /** Returns the path and its extracted params as a structured `URLObject`, or as a stringified expression when `stringify` is set.
526
+ *
527
+ * @example
528
+ * ```ts
529
+ * new URLPath('/pet/{petId}').object
530
+ * // { url: '/pet/:petId', params: { petId: 'petId' } }
531
+ * ```
532
+ */
594
533
  get object() {
595
534
  return this.toObject();
596
535
  }
597
- /** Returns a map of path parameter names, or `undefined` when the path has no parameters. */
536
+ /** Returns a map of path parameter names, or `undefined` when the path has no parameters.
537
+ *
538
+ * @example
539
+ * ```ts
540
+ * new URLPath('/pet/{petId}').params // { petId: 'petId' }
541
+ * new URLPath('/pet').params // undefined
542
+ * ```
543
+ */
598
544
  get params() {
599
545
  return this.getParams();
600
546
  }
@@ -602,7 +548,9 @@ var URLPath = class {
602
548
  const param = isValidVarName(raw) ? raw : camelCase(raw);
603
549
  return this.#options.casing === "camelcase" ? camelCase(param) : param;
604
550
  }
605
- /** Iterates over every `{param}` token in `path`, calling `fn` with the raw token and transformed name. */
551
+ /**
552
+ * Iterates over every `{param}` token in `path`, calling `fn` with the raw token and transformed name.
553
+ */
606
554
  #eachParam(fn) {
607
555
  for (const match of this.path.matchAll(/\{([^}]+)\}/g)) {
608
556
  const raw = match[1];
@@ -639,6 +587,12 @@ var URLPath = class {
639
587
  * Extracts all `{param}` segments from the path and returns them as a key-value map.
640
588
  * An optional `replacer` transforms each parameter name in both key and value positions.
641
589
  * Returns `undefined` when no path parameters are found.
590
+ *
591
+ * @example
592
+ * ```ts
593
+ * new URLPath('/pet/{petId}/tag/{tagId}').getParams()
594
+ * // { petId: 'petId', tagId: 'tagId' }
595
+ * ```
642
596
  */
643
597
  getParams(replacer) {
644
598
  const params = {};
@@ -648,29 +602,40 @@ var URLPath = class {
648
602
  });
649
603
  return Object.keys(params).length > 0 ? params : void 0;
650
604
  }
651
- /** Converts the OpenAPI path to Express-style colon syntax, e.g. `/pet/{petId}` → `/pet/:petId`. */
605
+ /** Converts the OpenAPI path to Express-style colon syntax.
606
+ *
607
+ * @example
608
+ * ```ts
609
+ * new URLPath('/pet/{petId}').toURLPath() // '/pet/:petId'
610
+ * ```
611
+ */
652
612
  toURLPath() {
653
613
  return this.path.replace(/\{([^}]+)\}/g, ":$1");
654
614
  }
655
615
  };
656
616
  //#endregion
657
- //#region src/config.ts
658
- function defineConfig(config) {
659
- return config;
660
- }
617
+ //#region src/constants.ts
661
618
  /**
662
- * Type guard to check if a given config has an `input.path`.
619
+ * Base URL for the Kubb Studio web app.
663
620
  */
664
- function isInputPath(config) {
665
- return typeof config?.input === "object" && config.input !== null && "path" in config.input;
666
- }
667
- //#endregion
668
- //#region src/constants.ts
669
621
  const DEFAULT_STUDIO_URL = "https://studio.kubb.dev";
622
+ /**
623
+ * File name used for generated barrel (index) files.
624
+ */
670
625
  const BARREL_FILENAME = "index.ts";
626
+ /**
627
+ * Default banner style written at the top of every generated file.
628
+ */
671
629
  const DEFAULT_BANNER = "simple";
630
+ /**
631
+ * Default file-extension mapping used when no explicit mapping is configured.
632
+ */
672
633
  const DEFAULT_EXTENSION = { ".ts": ".ts" };
673
- const PATH_SEPARATORS = ["/", "\\"];
634
+ /**
635
+ * Numeric log-level thresholds used internally to compare verbosity.
636
+ *
637
+ * Higher numbers are more verbose.
638
+ */
674
639
  const logLevel = {
675
640
  silent: Number.NEGATIVE_INFINITY,
676
641
  error: 0,
@@ -679,6 +644,13 @@ const logLevel = {
679
644
  verbose: 4,
680
645
  debug: 5
681
646
  };
647
+ /**
648
+ * CLI command descriptors for each supported linter.
649
+ *
650
+ * Each entry contains the executable `command`, an `args` factory that maps an
651
+ * output path to the correct argument list, and an `errorMessage` shown when
652
+ * the linter is not found.
653
+ */
682
654
  const linters = {
683
655
  eslint: {
684
656
  command: "eslint",
@@ -700,6 +672,13 @@ const linters = {
700
672
  errorMessage: "Oxlint not found"
701
673
  }
702
674
  };
675
+ /**
676
+ * CLI command descriptors for each supported code formatter.
677
+ *
678
+ * Each entry contains the executable `command`, an `args` factory that maps an
679
+ * output path to the correct argument list, and an `errorMessage` shown when
680
+ * the formatter is not found.
681
+ */
703
682
  const formatters = {
704
683
  prettier: {
705
684
  command: "prettier",
@@ -726,6 +705,34 @@ const formatters = {
726
705
  }
727
706
  };
728
707
  //#endregion
708
+ //#region src/defineParser.ts
709
+ /**
710
+ * Defines a parser with type safety.
711
+ *
712
+ * Use this function to create parsers that transform generated files to strings
713
+ * based on their extension.
714
+ *
715
+ * @example
716
+ * ```ts
717
+ * import { defineParser } from '@kubb/core'
718
+ *
719
+ * export const jsonParser = defineParser({
720
+ * name: 'json',
721
+ * extNames: ['.json'],
722
+ * parse(file) {
723
+ * return file.sources.map((s) => s.value).join('\n')
724
+ * },
725
+ * })
726
+ * ```
727
+ */
728
+ function defineParser(parser) {
729
+ return {
730
+ install() {},
731
+ type: "parser",
732
+ ...parser
733
+ };
734
+ }
735
+ //#endregion
729
736
  //#region src/devtools.ts
730
737
  /**
731
738
  * Encodes a `RootNode` as a compressed, URL-safe string.
@@ -899,7 +906,12 @@ function validateConcurrency(concurrency) {
899
906
  //#endregion
900
907
  //#region src/utils/executeStrategies.ts
901
908
  /**
902
- * Chains promises
909
+ * Runs promise functions in sequence, threading each result into the next call.
910
+ *
911
+ * - Each function receives the accumulated state from the previous call.
912
+ * - Skips functions that return a falsy value (acts as a no-op for that step).
913
+ * - Returns an array of all individual results.
914
+ * @deprecated
903
915
  */
904
916
  function hookSeq(promises) {
905
917
  return promises.filter(Boolean).reduce((promise, func) => {
@@ -912,7 +924,11 @@ function hookSeq(promises) {
912
924
  }, Promise.resolve([]));
913
925
  }
914
926
  /**
915
- * Chains promises, first non-null result stops and returns
927
+ * Runs promise functions in sequence and returns the first non-null result.
928
+ *
929
+ * - Stops as soon as `nullCheck` passes for a result (default: `!== null`).
930
+ * - Subsequent functions are skipped once a match is found.
931
+ * @deprecated
916
932
  */
917
933
  function hookFirst(promises, nullCheck = (state) => state !== null) {
918
934
  let promise = Promise.resolve(null);
@@ -923,7 +939,11 @@ function hookFirst(promises, nullCheck = (state) => state !== null) {
923
939
  return promise;
924
940
  }
925
941
  /**
926
- * Runs an array of promise functions with optional concurrency limit.
942
+ * Runs promise functions concurrently and returns all settled results.
943
+ *
944
+ * - Limits simultaneous executions to `concurrency` (default: unlimited).
945
+ * - Uses `Promise.allSettled` so individual failures do not cancel other tasks.
946
+ * @deprecated
927
947
  */
928
948
  function hookParallel(promises, concurrency = Number.POSITIVE_INFINITY) {
929
949
  const limit = pLimit(concurrency);
@@ -931,29 +951,22 @@ function hookParallel(promises, concurrency = Number.POSITIVE_INFINITY) {
931
951
  return Promise.allSettled(tasks);
932
952
  }
933
953
  //#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
954
+ //#region src/PluginDriver.ts
955
+ /**
956
+ * Returns `'single'` when `fileOrFolder` has a file extension, `'split'` otherwise.
957
+ *
958
+ * @example
959
+ * ```ts
960
+ * getMode('src/gen/types.ts') // 'single'
961
+ * getMode('src/gen/types') // 'split'
962
+ * ```
963
+ */
952
964
  function getMode(fileOrFolder) {
953
965
  if (!fileOrFolder) return "split";
954
966
  return extname(fileOrFolder) ? "single" : "split";
955
967
  }
956
- var PluginManager = class {
968
+ const hookFirstNullCheck = (state) => !!state?.result;
969
+ var PluginDriver = class {
957
970
  config;
958
971
  options;
959
972
  /**
@@ -963,31 +976,43 @@ var PluginManager = class {
963
976
  rootNode = void 0;
964
977
  adapter = void 0;
965
978
  #studioIsOpen = false;
966
- #plugins = /* @__PURE__ */ new Set();
967
- #usedPluginNames = {};
968
- #promiseManager;
979
+ plugins = /* @__PURE__ */ new Map();
969
980
  constructor(config, options) {
970
981
  this.config = config;
971
982
  this.options = options;
972
- this.#promiseManager = new PromiseManager({ nullCheck: (state) => !!state?.result });
973
- [...config.plugins || []].forEach((plugin) => {
974
- const parsedPlugin = this.#parse(plugin);
975
- this.#plugins.add(parsedPlugin);
983
+ config.plugins.map((plugin) => Object.assign({
984
+ buildStart() {},
985
+ buildEnd() {}
986
+ }, plugin)).filter((plugin) => {
987
+ if (typeof plugin.apply === "function") return plugin.apply(config);
988
+ return true;
989
+ }).sort((a, b) => {
990
+ if (b.pre?.includes(a.name)) return 1;
991
+ if (b.post?.includes(a.name)) return -1;
992
+ return 0;
993
+ }).forEach((plugin) => {
994
+ this.plugins.set(plugin.name, plugin);
976
995
  });
977
996
  }
978
997
  get events() {
979
998
  return this.options.events;
980
999
  }
981
1000
  getContext(plugin) {
982
- const plugins = [...this.#plugins];
983
- const pluginManager = this;
1001
+ const driver = this;
984
1002
  const baseContext = {
985
- fabric: this.options.fabric,
986
- config: this.config,
1003
+ fabric: driver.options.fabric,
1004
+ config: driver.config,
1005
+ get root() {
1006
+ return resolve(driver.config.root, driver.config.output.path);
1007
+ },
1008
+ getMode(output) {
1009
+ return getMode(resolve(driver.config.root, driver.config.output.path, output.path));
1010
+ },
1011
+ events: driver.options.events,
987
1012
  plugin,
988
- events: this.options.events,
989
- pluginManager: this,
990
- mode: getMode(resolve(this.config.root, this.config.output.path)),
1013
+ getPlugin: driver.getPlugin.bind(driver),
1014
+ requirePlugin: driver.requirePlugin.bind(driver),
1015
+ driver,
991
1016
  addFile: async (...files) => {
992
1017
  await this.options.fabric.addFile(...files);
993
1018
  },
@@ -995,23 +1020,38 @@ var PluginManager = class {
995
1020
  await this.options.fabric.upsertFile(...files);
996
1021
  },
997
1022
  get rootNode() {
998
- return pluginManager.rootNode;
1023
+ return driver.rootNode;
999
1024
  },
1000
1025
  get adapter() {
1001
- return pluginManager.adapter;
1026
+ return driver.adapter;
1027
+ },
1028
+ get resolver() {
1029
+ return plugin.resolver;
1030
+ },
1031
+ get transformer() {
1032
+ return plugin.transformer;
1033
+ },
1034
+ warn(message) {
1035
+ driver.events.emit("warn", message);
1036
+ },
1037
+ error(error) {
1038
+ driver.events.emit("error", typeof error === "string" ? new Error(error) : error);
1039
+ },
1040
+ info(message) {
1041
+ driver.events.emit("info", message);
1002
1042
  },
1003
1043
  openInStudio(options) {
1004
- if (!pluginManager.config.devtools || pluginManager.#studioIsOpen) return;
1005
- if (typeof pluginManager.config.devtools !== "object") throw new Error("Devtools must be an object");
1006
- if (!pluginManager.rootNode || !pluginManager.adapter) throw new Error("adapter is not defined, make sure you have set the parser in kubb.config.ts");
1007
- pluginManager.#studioIsOpen = true;
1008
- const studioUrl = pluginManager.config.devtools?.studioUrl ?? "https://studio.kubb.dev";
1009
- return openInStudio(pluginManager.rootNode, studioUrl, options);
1044
+ if (!driver.config.devtools || driver.#studioIsOpen) return;
1045
+ if (typeof driver.config.devtools !== "object") throw new Error("Devtools must be an object");
1046
+ if (!driver.rootNode || !driver.adapter) throw new Error("adapter is not defined, make sure you have set the parser in kubb.config.ts");
1047
+ driver.#studioIsOpen = true;
1048
+ const studioUrl = driver.config.devtools?.studioUrl ?? "https://studio.kubb.dev";
1049
+ return openInStudio(driver.rootNode, studioUrl, options);
1010
1050
  }
1011
1051
  };
1012
1052
  const mergedExtras = {};
1013
- for (const p of plugins) if (typeof p.inject === "function") {
1014
- const result = p.inject.call(baseContext, baseContext);
1053
+ for (const p of this.plugins.values()) if (typeof p.inject === "function") {
1054
+ const result = p.inject.call(baseContext);
1015
1055
  if (result !== null && typeof result === "object") Object.assign(mergedExtras, result);
1016
1056
  }
1017
1057
  return {
@@ -1019,9 +1059,9 @@ var PluginManager = class {
1019
1059
  ...mergedExtras
1020
1060
  };
1021
1061
  }
1022
- get plugins() {
1023
- return this.#getSortedPlugins();
1024
- }
1062
+ /**
1063
+ * @deprecated use resolvers context instead
1064
+ */
1025
1065
  getFile({ name, mode, extname, pluginName, options }) {
1026
1066
  const resolvedName = mode ? mode === "single" ? "" : this.resolveName({
1027
1067
  name,
@@ -1044,6 +1084,9 @@ var PluginManager = class {
1044
1084
  exports: []
1045
1085
  };
1046
1086
  }
1087
+ /**
1088
+ * @deprecated use resolvers context instead
1089
+ */
1047
1090
  resolvePath = (params) => {
1048
1091
  const defaultPath = resolve(resolve(this.config.root, this.config.output.path), params.baseName);
1049
1092
  if (params.pluginName) return this.hookForPluginSync({
@@ -1064,15 +1107,15 @@ var PluginManager = class {
1064
1107
  ]
1065
1108
  })?.result || defaultPath;
1066
1109
  };
1110
+ /**
1111
+ * @deprecated use resolvers context instead
1112
+ */
1067
1113
  resolveName = (params) => {
1068
- if (params.pluginName) {
1069
- const names = this.hookForPluginSync({
1070
- pluginName: params.pluginName,
1071
- hookName: "resolveName",
1072
- parameters: [params.name.trim(), params.type]
1073
- });
1074
- return transformReservedWord([...new Set(names)].at(0) || params.name);
1075
- }
1114
+ if (params.pluginName) return transformReservedWord(this.hookForPluginSync({
1115
+ pluginName: params.pluginName,
1116
+ hookName: "resolveName",
1117
+ parameters: [params.name.trim(), params.type]
1118
+ })?.at(0) ?? params.name);
1076
1119
  const name = this.hookFirstSync({
1077
1120
  hookName: "resolveName",
1078
1121
  parameters: [params.name.trim(), params.type]
@@ -1083,49 +1126,46 @@ var PluginManager = class {
1083
1126
  * Run a specific hookName for plugin x.
1084
1127
  */
1085
1128
  async hookForPlugin({ pluginName, hookName, parameters }) {
1086
- const plugins = this.getPluginsByName(hookName, pluginName);
1129
+ const plugin = this.plugins.get(pluginName);
1130
+ if (!plugin) return [null];
1087
1131
  this.events.emit("plugins:hook:progress:start", {
1088
1132
  hookName,
1089
- plugins
1133
+ plugins: [plugin]
1134
+ });
1135
+ const result = await this.#execute({
1136
+ strategy: "hookFirst",
1137
+ hookName,
1138
+ parameters,
1139
+ plugin
1090
1140
  });
1091
- const items = [];
1092
- for (const plugin of plugins) {
1093
- const result = await this.#execute({
1094
- strategy: "hookFirst",
1095
- hookName,
1096
- parameters,
1097
- plugin
1098
- });
1099
- if (result !== void 0 && result !== null) items.push(result);
1100
- }
1101
1141
  this.events.emit("plugins:hook:progress:end", { hookName });
1102
- return items;
1142
+ return [result];
1103
1143
  }
1104
1144
  /**
1105
1145
  * Run a specific hookName for plugin x.
1106
1146
  */
1107
1147
  hookForPluginSync({ pluginName, hookName, parameters }) {
1108
- return this.getPluginsByName(hookName, pluginName).map((plugin) => {
1109
- return this.#executeSync({
1110
- strategy: "hookFirst",
1111
- hookName,
1112
- parameters,
1113
- plugin
1114
- });
1115
- }).filter((x) => x !== null);
1148
+ const plugin = this.plugins.get(pluginName);
1149
+ if (!plugin) return null;
1150
+ const result = this.#executeSync({
1151
+ strategy: "hookFirst",
1152
+ hookName,
1153
+ parameters,
1154
+ plugin
1155
+ });
1156
+ return result !== null ? [result] : [];
1116
1157
  }
1117
1158
  /**
1118
1159
  * Returns the first non-null result.
1119
1160
  */
1120
1161
  async hookFirst({ hookName, parameters, skipped }) {
1121
- const plugins = this.#getSortedPlugins(hookName).filter((plugin) => {
1122
- return skipped ? !skipped.has(plugin) : true;
1123
- });
1162
+ const plugins = [];
1163
+ for (const plugin of this.plugins.values()) if (hookName in plugin && (skipped ? !skipped.has(plugin) : true)) plugins.push(plugin);
1124
1164
  this.events.emit("plugins:hook:progress:start", {
1125
1165
  hookName,
1126
1166
  plugins
1127
1167
  });
1128
- const promises = plugins.map((plugin) => {
1168
+ const result = await hookFirst(plugins.map((plugin) => {
1129
1169
  return async () => {
1130
1170
  const value = await this.#execute({
1131
1171
  strategy: "hookFirst",
@@ -1138,8 +1178,7 @@ var PluginManager = class {
1138
1178
  result: value
1139
1179
  });
1140
1180
  };
1141
- });
1142
- const result = await this.#promiseManager.run("first", promises);
1181
+ }), hookFirstNullCheck);
1143
1182
  this.events.emit("plugins:hook:progress:end", { hookName });
1144
1183
  return result;
1145
1184
  }
@@ -1148,10 +1187,9 @@ var PluginManager = class {
1148
1187
  */
1149
1188
  hookFirstSync({ hookName, parameters, skipped }) {
1150
1189
  let parseResult = null;
1151
- const plugins = this.#getSortedPlugins(hookName).filter((plugin) => {
1152
- return skipped ? !skipped.has(plugin) : true;
1153
- });
1154
- for (const plugin of plugins) {
1190
+ for (const plugin of this.plugins.values()) {
1191
+ if (!(hookName in plugin)) continue;
1192
+ if (skipped?.has(plugin)) continue;
1155
1193
  parseResult = {
1156
1194
  result: this.#executeSync({
1157
1195
  strategy: "hookFirst",
@@ -1161,7 +1199,7 @@ var PluginManager = class {
1161
1199
  }),
1162
1200
  plugin
1163
1201
  };
1164
- if (parseResult?.result != null) break;
1202
+ if (parseResult.result != null) break;
1165
1203
  }
1166
1204
  return parseResult;
1167
1205
  }
@@ -1169,13 +1207,14 @@ var PluginManager = class {
1169
1207
  * Runs all plugins in parallel based on `this.plugin` order and `pre`/`post` settings.
1170
1208
  */
1171
1209
  async hookParallel({ hookName, parameters }) {
1172
- const plugins = this.#getSortedPlugins(hookName);
1210
+ const plugins = [];
1211
+ for (const plugin of this.plugins.values()) if (hookName in plugin) plugins.push(plugin);
1173
1212
  this.events.emit("plugins:hook:progress:start", {
1174
1213
  hookName,
1175
1214
  plugins
1176
1215
  });
1177
1216
  const pluginStartTimes = /* @__PURE__ */ new Map();
1178
- const promises = plugins.map((plugin) => {
1217
+ const results = await hookParallel(plugins.map((plugin) => {
1179
1218
  return () => {
1180
1219
  pluginStartTimes.set(plugin, performance.now());
1181
1220
  return this.#execute({
@@ -1185,11 +1224,10 @@ var PluginManager = class {
1185
1224
  plugin
1186
1225
  });
1187
1226
  };
1188
- });
1189
- const results = await this.#promiseManager.run("parallel", promises, { concurrency: this.options.concurrency });
1227
+ }), this.options.concurrency);
1190
1228
  results.forEach((result, index) => {
1191
1229
  if (isPromiseRejectedResult(result)) {
1192
- const plugin = this.#getSortedPlugins(hookName)[index];
1230
+ const plugin = plugins[index];
1193
1231
  if (plugin) {
1194
1232
  const startTime = pluginStartTimes.get(plugin) ?? performance.now();
1195
1233
  this.events.emit("error", result.reason, {
@@ -1212,49 +1250,29 @@ var PluginManager = class {
1212
1250
  * Chains plugins
1213
1251
  */
1214
1252
  async hookSeq({ hookName, parameters }) {
1215
- const plugins = this.#getSortedPlugins(hookName);
1253
+ const plugins = [];
1254
+ for (const plugin of this.plugins.values()) if (hookName in plugin) plugins.push(plugin);
1216
1255
  this.events.emit("plugins:hook:progress:start", {
1217
1256
  hookName,
1218
1257
  plugins
1219
1258
  });
1220
- const promises = plugins.map((plugin) => {
1259
+ await hookSeq(plugins.map((plugin) => {
1221
1260
  return () => this.#execute({
1222
1261
  strategy: "hookSeq",
1223
1262
  hookName,
1224
1263
  parameters,
1225
1264
  plugin
1226
1265
  });
1227
- });
1228
- await this.#promiseManager.run("seq", promises);
1266
+ }));
1229
1267
  this.events.emit("plugins:hook:progress:end", { hookName });
1230
1268
  }
1231
- #getSortedPlugins(hookName) {
1232
- const plugins = [...this.#plugins];
1233
- if (hookName) return plugins.filter((plugin) => hookName in plugin);
1234
- return plugins.map((plugin) => {
1235
- if (plugin.pre) {
1236
- let missingPlugins = plugin.pre.filter((pluginName) => !plugins.find((pluginToFind) => pluginToFind.name === pluginName));
1237
- if (missingPlugins.includes("plugin-oas") && this.adapter) missingPlugins = missingPlugins.filter((pluginName) => pluginName !== "plugin-oas");
1238
- if (missingPlugins.length > 0) throw new ValidationPluginError(`The plugin '${plugin.name}' has a pre set that references missing plugins for '${missingPlugins.join(", ")}'`);
1239
- }
1240
- return plugin;
1241
- }).sort((a, b) => {
1242
- if (b.pre?.includes(a.name)) return 1;
1243
- if (b.post?.includes(a.name)) return -1;
1244
- return 0;
1245
- });
1246
- }
1247
- getPluginByName(pluginName) {
1248
- return [...this.#plugins].find((item) => item.name === pluginName);
1269
+ getPlugin(pluginName) {
1270
+ return this.plugins.get(pluginName);
1249
1271
  }
1250
- getPluginsByName(hookName, pluginName) {
1251
- const plugins = [...this.plugins];
1252
- const pluginByPluginName = plugins.filter((plugin) => hookName in plugin).filter((item) => item.name === pluginName);
1253
- if (!pluginByPluginName?.length) {
1254
- const corePlugin = plugins.find((plugin) => plugin.name === "core" && hookName in plugin);
1255
- return corePlugin ? [corePlugin] : [];
1256
- }
1257
- return pluginByPluginName;
1272
+ requirePlugin(pluginName) {
1273
+ const plugin = this.plugins.get(pluginName);
1274
+ if (!plugin) throw new Error(`[kubb] Plugin "${pluginName}" is required but not found. Make sure it is included in your Kubb config.`);
1275
+ return plugin;
1258
1276
  }
1259
1277
  /**
1260
1278
  * Run an async plugin hook and return the result.
@@ -1342,31 +1360,34 @@ var PluginManager = class {
1342
1360
  return null;
1343
1361
  }
1344
1362
  }
1345
- #parse(plugin) {
1346
- const usedPluginNames = this.#usedPluginNames;
1347
- setUniqueName(plugin.name, usedPluginNames);
1348
- const usageCount = usedPluginNames[plugin.name];
1349
- if (usageCount && usageCount > 1) throw new ValidationPluginError(`Duplicate plugin "${plugin.name}" detected. Each plugin can only be used once. Use a different configuration instead of adding multiple instances of the same plugin.`);
1350
- return {
1351
- install() {},
1352
- ...plugin
1353
- };
1354
- }
1355
1363
  };
1356
1364
  //#endregion
1357
- //#region src/defineStorage.ts
1365
+ //#region src/renderNode.tsx
1358
1366
  /**
1359
- * Wraps a storage builder so the `options` argument is optional, following the
1360
- * same factory pattern as `definePlugin`, `defineLogger`, and `defineAdapter`.
1367
+ * Handles the return value of a plugin AST hook or generator method.
1361
1368
  *
1362
- * The builder receives the resolved options object and must return a
1363
- * `DefineStorage`-compatible object that includes a `name` string.
1369
+ * - React element rendered via an isolated react-fabric context, files merged into `fabric`
1370
+ * - `Array<KubbFile.File>` upserted directly into `fabric`
1371
+ * - `void` / `null` / `undefined` → no-op (plugin handled it via `this.upsertFile`)
1372
+ */
1373
+ async function applyHookResult(result, fabric) {
1374
+ if (!result) return;
1375
+ if (Array.isArray(result)) {
1376
+ await fabric.upsertFile(...result);
1377
+ return;
1378
+ }
1379
+ const fabricChild = createReactFabric();
1380
+ await fabricChild.render(/* @__PURE__ */ jsx(Fabric, { children: result }));
1381
+ fabric.context.fileManager.upsert(...fabricChild.files);
1382
+ fabricChild.unmount();
1383
+ }
1384
+ //#endregion
1385
+ //#region src/createStorage.ts
1386
+ /**
1387
+ * Creates a storage factory. Call the returned function with optional options to get the storage instance.
1364
1388
  *
1365
1389
  * @example
1366
- * ```ts
1367
- * import { defineStorage } from '@kubb/core'
1368
- *
1369
- * export const memoryStorage = defineStorage((_options) => {
1390
+ * export const memoryStorage = createStorage(() => {
1370
1391
  * const store = new Map<string, string>()
1371
1392
  * return {
1372
1393
  * name: 'memory',
@@ -1374,13 +1395,15 @@ var PluginManager = class {
1374
1395
  * async getItem(key) { return store.get(key) ?? null },
1375
1396
  * async setItem(key, value) { store.set(key, value) },
1376
1397
  * async removeItem(key) { store.delete(key) },
1377
- * async getKeys() { return [...store.keys()] },
1378
- * async clear() { store.clear() },
1398
+ * async getKeys(base) {
1399
+ * const keys = [...store.keys()]
1400
+ * return base ? keys.filter((k) => k.startsWith(base)) : keys
1401
+ * },
1402
+ * async clear(base) { if (!base) store.clear() },
1379
1403
  * }
1380
1404
  * })
1381
- * ```
1382
1405
  */
1383
- function defineStorage(build) {
1406
+ function createStorage(build) {
1384
1407
  return (options) => build(options ?? {});
1385
1408
  }
1386
1409
  //#endregion
@@ -1408,7 +1431,7 @@ function defineStorage(build) {
1408
1431
  * })
1409
1432
  * ```
1410
1433
  */
1411
- const fsStorage = defineStorage(() => ({
1434
+ const fsStorage = createStorage(() => ({
1412
1435
  name: "fs",
1413
1436
  async hasItem(key) {
1414
1437
  try {
@@ -1456,11 +1479,14 @@ const fsStorage = defineStorage(() => ({
1456
1479
  }));
1457
1480
  //#endregion
1458
1481
  //#region package.json
1459
- var version$1 = "5.0.0-alpha.3";
1482
+ var version$1 = "5.0.0-alpha.31";
1460
1483
  //#endregion
1461
1484
  //#region src/utils/diagnostics.ts
1462
1485
  /**
1463
- * Get diagnostic information for debugging
1486
+ * Returns a snapshot of the current runtime environment.
1487
+ *
1488
+ * Useful for attaching context to debug logs and error reports so that
1489
+ * issues can be reproduced without manual information gathering.
1464
1490
  */
1465
1491
  function getDiagnosticInfo() {
1466
1492
  return {
@@ -1472,7 +1498,262 @@ function getDiagnosticInfo() {
1472
1498
  };
1473
1499
  }
1474
1500
  //#endregion
1501
+ //#region src/utils/TreeNode.ts
1502
+ /**
1503
+ * Tree structure used to build per-directory barrel (`index.ts`) files from a
1504
+ * flat list of generated {@link KubbFile.File} entries.
1505
+ *
1506
+ * Each node represents either a directory or a file within the output tree.
1507
+ * Use {@link TreeNode.build} to construct a root node from a file list, then
1508
+ * traverse with {@link TreeNode.forEach}, {@link TreeNode.leaves}, or the
1509
+ * `*Deep` helpers.
1510
+ */
1511
+ var TreeNode = class TreeNode {
1512
+ data;
1513
+ parent;
1514
+ children = [];
1515
+ #cachedLeaves = void 0;
1516
+ constructor(data, parent) {
1517
+ this.data = data;
1518
+ this.parent = parent;
1519
+ }
1520
+ addChild(data) {
1521
+ const child = new TreeNode(data, this);
1522
+ if (!this.children) this.children = [];
1523
+ this.children.push(child);
1524
+ return child;
1525
+ }
1526
+ /**
1527
+ * Returns the root ancestor of this node, walking up via `parent` links.
1528
+ */
1529
+ get root() {
1530
+ if (!this.parent) return this;
1531
+ return this.parent.root;
1532
+ }
1533
+ /**
1534
+ * Returns all leaf descendants (nodes with no children) of this node.
1535
+ *
1536
+ * Results are cached after the first traversal.
1537
+ */
1538
+ get leaves() {
1539
+ if (!this.children || this.children.length === 0) return [this];
1540
+ if (this.#cachedLeaves) return this.#cachedLeaves;
1541
+ const leaves = [];
1542
+ for (const child of this.children) leaves.push(...child.leaves);
1543
+ this.#cachedLeaves = leaves;
1544
+ return leaves;
1545
+ }
1546
+ /**
1547
+ * Visits this node and every descendant in depth-first order.
1548
+ */
1549
+ forEach(callback) {
1550
+ if (typeof callback !== "function") throw new TypeError("forEach() callback must be a function");
1551
+ callback(this);
1552
+ for (const child of this.children) child.forEach(callback);
1553
+ return this;
1554
+ }
1555
+ /**
1556
+ * Finds the first leaf that satisfies `predicate`, or `undefined` when none match.
1557
+ */
1558
+ findDeep(predicate) {
1559
+ if (typeof predicate !== "function") throw new TypeError("find() predicate must be a function");
1560
+ return this.leaves.find(predicate);
1561
+ }
1562
+ /**
1563
+ * Calls `callback` for every leaf of this node.
1564
+ */
1565
+ forEachDeep(callback) {
1566
+ if (typeof callback !== "function") throw new TypeError("forEach() callback must be a function");
1567
+ this.leaves.forEach(callback);
1568
+ }
1569
+ /**
1570
+ * Returns all leaves that satisfy `callback`.
1571
+ */
1572
+ filterDeep(callback) {
1573
+ if (typeof callback !== "function") throw new TypeError("filter() callback must be a function");
1574
+ return this.leaves.filter(callback);
1575
+ }
1576
+ /**
1577
+ * Maps every leaf through `callback` and returns the resulting array.
1578
+ */
1579
+ mapDeep(callback) {
1580
+ if (typeof callback !== "function") throw new TypeError("map() callback must be a function");
1581
+ return this.leaves.map(callback);
1582
+ }
1583
+ /**
1584
+ * Builds a {@link TreeNode} tree from a flat list of files.
1585
+ *
1586
+ * - Filters to files under `root` (when provided) and skips `.json` files.
1587
+ * - Returns `null` when no files match.
1588
+ */
1589
+ static build(files, root) {
1590
+ try {
1591
+ const filteredTree = buildDirectoryTree(files, root);
1592
+ if (!filteredTree) return null;
1593
+ const treeNode = new TreeNode({
1594
+ name: filteredTree.name,
1595
+ path: filteredTree.path,
1596
+ file: filteredTree.file,
1597
+ type: getMode(filteredTree.path)
1598
+ });
1599
+ const recurse = (node, item) => {
1600
+ const subNode = node.addChild({
1601
+ name: item.name,
1602
+ path: item.path,
1603
+ file: item.file,
1604
+ type: getMode(item.path)
1605
+ });
1606
+ if (item.children?.length) item.children?.forEach((child) => {
1607
+ recurse(subNode, child);
1608
+ });
1609
+ };
1610
+ filteredTree.children?.forEach((child) => {
1611
+ recurse(treeNode, child);
1612
+ });
1613
+ return treeNode;
1614
+ } catch (error) {
1615
+ throw new Error("Something went wrong with creating barrel files with the TreeNode class", { cause: error });
1616
+ }
1617
+ }
1618
+ };
1619
+ const normalizePath = (p) => p.replaceAll("\\", "/");
1620
+ function buildDirectoryTree(files, rootFolder = "") {
1621
+ const normalizedRootFolder = normalizePath(rootFolder);
1622
+ const rootPrefix = normalizedRootFolder.endsWith("/") ? normalizedRootFolder : `${normalizedRootFolder}/`;
1623
+ const filteredFiles = files.filter((file) => {
1624
+ const normalizedFilePath = normalizePath(file.path);
1625
+ return rootFolder ? normalizedFilePath.startsWith(rootPrefix) && !normalizedFilePath.endsWith(".json") : !normalizedFilePath.endsWith(".json");
1626
+ });
1627
+ if (filteredFiles.length === 0) return null;
1628
+ const root = {
1629
+ name: rootFolder || "",
1630
+ path: rootFolder || "",
1631
+ children: []
1632
+ };
1633
+ filteredFiles.forEach((file) => {
1634
+ const parts = file.path.slice(rootFolder.length).split("/").filter(Boolean);
1635
+ let currentLevel = root.children;
1636
+ let currentPath = normalizePath(rootFolder);
1637
+ parts.forEach((part, index) => {
1638
+ currentPath = path.posix.join(currentPath, part);
1639
+ let existingNode = currentLevel.find((node) => node.name === part);
1640
+ if (!existingNode) {
1641
+ if (index === parts.length - 1) existingNode = {
1642
+ name: part,
1643
+ file,
1644
+ path: currentPath
1645
+ };
1646
+ else existingNode = {
1647
+ name: part,
1648
+ path: currentPath,
1649
+ children: []
1650
+ };
1651
+ currentLevel.push(existingNode);
1652
+ }
1653
+ if (!existingNode.file) currentLevel = existingNode.children;
1654
+ });
1655
+ });
1656
+ return root;
1657
+ }
1658
+ //#endregion
1659
+ //#region src/utils/getBarrelFiles.ts
1660
+ /** biome-ignore-all lint/suspicious/useIterableCallbackReturn: not needed */
1661
+ function getBarrelFilesByRoot(root, files) {
1662
+ const cachedFiles = /* @__PURE__ */ new Map();
1663
+ TreeNode.build(files, root)?.forEach((treeNode) => {
1664
+ if (!treeNode?.children || !treeNode.parent?.data.path) return;
1665
+ const barrelFile = {
1666
+ path: join(treeNode.parent?.data.path, "index.ts"),
1667
+ baseName: "index.ts",
1668
+ exports: [],
1669
+ imports: [],
1670
+ sources: []
1671
+ };
1672
+ const previousBarrelFile = cachedFiles.get(barrelFile.path);
1673
+ treeNode.leaves.forEach((item) => {
1674
+ if (!item.data.name) return;
1675
+ (item.data.file?.sources || []).forEach((source) => {
1676
+ if (!item.data.file?.path || !source.isIndexable || !source.name) return;
1677
+ if (previousBarrelFile?.sources.some((item) => item.name === source.name && item.isTypeOnly === source.isTypeOnly)) return;
1678
+ barrelFile.exports.push({
1679
+ name: [source.name],
1680
+ path: getRelativePath(treeNode.parent?.data.path, item.data.path),
1681
+ isTypeOnly: source.isTypeOnly
1682
+ });
1683
+ barrelFile.sources.push({
1684
+ name: source.name,
1685
+ isTypeOnly: source.isTypeOnly,
1686
+ value: "",
1687
+ isExportable: false,
1688
+ isIndexable: false
1689
+ });
1690
+ });
1691
+ });
1692
+ if (previousBarrelFile) {
1693
+ previousBarrelFile.sources.push(...barrelFile.sources);
1694
+ previousBarrelFile.exports?.push(...barrelFile.exports || []);
1695
+ } else cachedFiles.set(barrelFile.path, barrelFile);
1696
+ });
1697
+ return [...cachedFiles.values()];
1698
+ }
1699
+ function trimExtName(text) {
1700
+ const dotIndex = text.lastIndexOf(".");
1701
+ if (dotIndex > 0 && !text.includes("/", dotIndex)) return text.slice(0, dotIndex);
1702
+ return text;
1703
+ }
1704
+ /**
1705
+ * Generates `index.ts` barrel files for all directories under `root/output.path`.
1706
+ *
1707
+ * - Returns an empty array when `type` is falsy or `'propagate'`.
1708
+ * - Skips generation when the output path itself ends with `index` (already a barrel).
1709
+ * - When `type` is `'all'`, strips named exports so every re-export becomes a wildcard (`export * from`).
1710
+ * - Attaches `meta` to each barrel file for downstream plugin identification.
1711
+ */
1712
+ async function getBarrelFiles(files, { type, meta = {}, root, output }) {
1713
+ if (!type || type === "propagate") return [];
1714
+ const pathToBuildFrom = join(root, output.path);
1715
+ if (trimExtName(pathToBuildFrom).endsWith("index")) return [];
1716
+ const barrelFiles = getBarrelFilesByRoot(pathToBuildFrom, files);
1717
+ if (type === "all") return barrelFiles.map((file) => {
1718
+ return {
1719
+ ...file,
1720
+ exports: file.exports?.map((exportItem) => {
1721
+ return {
1722
+ ...exportItem,
1723
+ name: void 0
1724
+ };
1725
+ })
1726
+ };
1727
+ });
1728
+ return barrelFiles.map((indexFile) => {
1729
+ return {
1730
+ ...indexFile,
1731
+ meta
1732
+ };
1733
+ });
1734
+ }
1735
+ //#endregion
1736
+ //#region src/utils/isInputPath.ts
1737
+ /**
1738
+ * Type guard to check if a given config has an `input.path`.
1739
+ */
1740
+ function isInputPath(config) {
1741
+ return typeof config?.input === "object" && config.input !== null && "path" in config.input;
1742
+ }
1743
+ //#endregion
1475
1744
  //#region src/build.ts
1745
+ /**
1746
+ * Initializes all Kubb infrastructure for a build without executing any plugins.
1747
+ *
1748
+ * - Validates the input path (when applicable).
1749
+ * - Applies config defaults (`root`, `output.*`, `devtools`).
1750
+ * - Creates the Fabric instance and wires storage, format, and lint hooks.
1751
+ * - Runs the adapter (if configured) to produce the universal `RootNode`.
1752
+ * When no adapter is supplied and `@kubb/adapter-oas` is installed as an
1753
+ *
1754
+ * Pass the returned {@link SetupResult} directly to {@link safeBuild} or {@link build}
1755
+ * via the `overrides` argument to reuse the same infrastructure across multiple runs.
1756
+ */
1476
1757
  async function setup(options) {
1477
1758
  const { config: userConfig, events = new AsyncEventEmitter() } = options;
1478
1759
  const sources = /* @__PURE__ */ new Map();
@@ -1508,9 +1789,12 @@ async function setup(options) {
1508
1789
  throw new Error(`Cannot read file/URL defined in \`input.path\` or set with \`kubb generate PATH\` in the CLI of your Kubb config ${userConfig.input.path}`, { cause: error });
1509
1790
  }
1510
1791
  }
1511
- const definedConfig = {
1792
+ if (!userConfig.adapter) throw new Error("Adapter should be defined");
1793
+ const config = {
1512
1794
  root: userConfig.root || process.cwd(),
1513
1795
  ...userConfig,
1796
+ parsers: userConfig.parsers ?? [],
1797
+ adapter: userConfig.adapter,
1514
1798
  output: {
1515
1799
  write: true,
1516
1800
  barrelType: "named",
@@ -1524,17 +1808,24 @@ async function setup(options) {
1524
1808
  } : void 0,
1525
1809
  plugins: userConfig.plugins
1526
1810
  };
1527
- const storage = definedConfig.output.write === false ? null : definedConfig.output.storage ?? fsStorage();
1528
- if (definedConfig.output.clean) {
1811
+ const storage = config.output.write === false ? null : config.output.storage ?? fsStorage();
1812
+ if (config.output.clean) {
1529
1813
  await events.emit("debug", {
1530
1814
  date: /* @__PURE__ */ new Date(),
1531
- logs: ["Cleaning output directories", ` • Output: ${definedConfig.output.path}`]
1815
+ logs: ["Cleaning output directories", ` • Output: ${config.output.path}`]
1532
1816
  });
1533
- await storage?.clear(resolve(definedConfig.root, definedConfig.output.path));
1817
+ await storage?.clear(resolve(config.root, config.output.path));
1534
1818
  }
1535
1819
  const fabric = createFabric();
1536
1820
  fabric.use(fsPlugin);
1537
- fabric.use(typescriptParser);
1821
+ for (const parser of config.parsers) fabric.use(parser);
1822
+ fabric.use(defineParser({
1823
+ name: "fallback",
1824
+ extNames: void 0,
1825
+ parse(file) {
1826
+ return file.sources.map((item) => item.value).filter((value) => value != null).join("\n\n");
1827
+ }
1828
+ }));
1538
1829
  fabric.context.on("files:processing:start", (files) => {
1539
1830
  events.emit("files:processing:start", files);
1540
1831
  events.emit("debug", {
@@ -1546,11 +1837,11 @@ async function setup(options) {
1546
1837
  const { file, source } = params;
1547
1838
  await events.emit("file:processing:update", {
1548
1839
  ...params,
1549
- config: definedConfig,
1840
+ config,
1550
1841
  source
1551
1842
  });
1552
1843
  if (source) {
1553
- const key = relative(resolve(definedConfig.root), file.path);
1844
+ const key = relative(resolve(config.root), file.path);
1554
1845
  await storage?.setItem(key, source);
1555
1846
  sources.set(file.path, source);
1556
1847
  }
@@ -1567,40 +1858,47 @@ async function setup(options) {
1567
1858
  logs: [
1568
1859
  "✓ Fabric initialized",
1569
1860
  ` • Storage: ${storage ? storage.name : "disabled (dry-run)"}`,
1570
- ` • Barrel type: ${definedConfig.output.barrelType || "none"}`
1861
+ ` • Barrel type: ${config.output.barrelType || "none"}`
1571
1862
  ]
1572
1863
  });
1573
- const pluginManager = new PluginManager(definedConfig, {
1864
+ const driver = new PluginDriver(config, {
1574
1865
  fabric,
1575
1866
  events,
1576
1867
  concurrency: 15
1577
1868
  });
1578
- if (definedConfig.adapter) {
1579
- const source = inputToAdapterSource(definedConfig);
1580
- await events.emit("debug", {
1581
- date: /* @__PURE__ */ new Date(),
1582
- logs: [`Running adapter: ${definedConfig.adapter.name}`]
1583
- });
1584
- pluginManager.adapter = definedConfig.adapter;
1585
- pluginManager.rootNode = await definedConfig.adapter.parse(source);
1586
- await events.emit("debug", {
1587
- date: /* @__PURE__ */ new Date(),
1588
- logs: [
1589
- `✓ Adapter '${definedConfig.adapter.name}' resolved RootNode`,
1590
- ` • Schemas: ${pluginManager.rootNode.schemas.length}`,
1591
- ` • Operations: ${pluginManager.rootNode.operations.length}`
1592
- ]
1593
- });
1594
- }
1869
+ const adapter = config.adapter;
1870
+ if (!adapter) throw new Error("No adapter configured. Please provide an adapter in your kubb.config.ts.");
1871
+ const source = inputToAdapterSource(config);
1872
+ await events.emit("debug", {
1873
+ date: /* @__PURE__ */ new Date(),
1874
+ logs: [`Running adapter: ${adapter.name}`]
1875
+ });
1876
+ driver.adapter = adapter;
1877
+ driver.rootNode = await adapter.parse(source);
1878
+ await events.emit("debug", {
1879
+ date: /* @__PURE__ */ new Date(),
1880
+ logs: [
1881
+ `✓ Adapter '${adapter.name}' resolved RootNode`,
1882
+ ` • Schemas: ${driver.rootNode.schemas.length}`,
1883
+ ` • Operations: ${driver.rootNode.operations.length}`
1884
+ ]
1885
+ });
1595
1886
  return {
1887
+ config,
1596
1888
  events,
1597
1889
  fabric,
1598
- pluginManager,
1890
+ driver,
1599
1891
  sources
1600
1892
  };
1601
1893
  }
1894
+ /**
1895
+ * Runs a full Kubb build and throws on any error or plugin failure.
1896
+ *
1897
+ * Internally delegates to {@link safeBuild} and rethrows collected errors.
1898
+ * Pass an existing {@link SetupResult} via `overrides` to skip the setup phase.
1899
+ */
1602
1900
  async function build(options, overrides) {
1603
- const { fabric, files, pluginManager, failedPlugins, pluginTimings, error, sources } = await safeBuild(options, overrides);
1901
+ const { fabric, files, driver, failedPlugins, pluginTimings, error, sources } = await safeBuild(options, overrides);
1604
1902
  if (error) throw error;
1605
1903
  if (failedPlugins.size > 0) {
1606
1904
  const errors = [...failedPlugins].map(({ error }) => error);
@@ -1610,30 +1908,96 @@ async function build(options, overrides) {
1610
1908
  failedPlugins,
1611
1909
  fabric,
1612
1910
  files,
1613
- pluginManager,
1911
+ driver,
1614
1912
  pluginTimings,
1615
1913
  error: void 0,
1616
1914
  sources
1617
1915
  };
1618
1916
  }
1917
+ /**
1918
+ * Walks the AST and dispatches nodes to a plugin's direct AST hooks
1919
+ * (`schema`, `operation`, `operations`).
1920
+ *
1921
+ * - Each hook accepts a single handler **or an array** — all entries are called in sequence.
1922
+ * - Nodes that are excluded by `exclude`/`include` plugin options are skipped automatically.
1923
+ * - Return values are handled via `applyHookResult`: React elements are rendered,
1924
+ * `KubbFile.File[]` are written via upsert, and `void` is a no-op (manual handling).
1925
+ * - Barrel files are generated automatically when `output.barrelType` is set.
1926
+ */
1927
+ async function runPluginAstHooks(plugin, context) {
1928
+ const { adapter, rootNode, resolver, fabric } = context;
1929
+ const { exclude, include, override } = plugin.options;
1930
+ if (!adapter || !rootNode) throw new Error(`[${plugin.name}] No adapter found. Add an OAS adapter (e.g. pluginOas()) before this plugin in your Kubb config.`);
1931
+ const collectedOperations = [];
1932
+ await walk(rootNode, {
1933
+ depth: "shallow",
1934
+ async schema(node) {
1935
+ if (!plugin.schema) return;
1936
+ const transformedNode = plugin.transformer ? transform(node, plugin.transformer) : node;
1937
+ const options = resolver.resolveOptions(transformedNode, {
1938
+ options: plugin.options,
1939
+ exclude,
1940
+ include,
1941
+ override
1942
+ });
1943
+ if (options === null) return;
1944
+ await applyHookResult(await plugin.schema.call(context, transformedNode, options), fabric);
1945
+ },
1946
+ async operation(node) {
1947
+ const transformedNode = plugin.transformer ? transform(node, plugin.transformer) : node;
1948
+ const options = resolver.resolveOptions(transformedNode, {
1949
+ options: plugin.options,
1950
+ exclude,
1951
+ include,
1952
+ override
1953
+ });
1954
+ if (options !== null) {
1955
+ collectedOperations.push(transformedNode);
1956
+ if (plugin.operation) await applyHookResult(await plugin.operation.call(context, transformedNode, options), fabric);
1957
+ }
1958
+ }
1959
+ });
1960
+ if (plugin.operations && collectedOperations.length > 0) await applyHookResult(await plugin.operations.call(context, collectedOperations, plugin.options), fabric);
1961
+ }
1962
+ /**
1963
+ * Runs a full Kubb build and captures errors instead of throwing.
1964
+ *
1965
+ * - Installs each plugin in order, recording failures in `failedPlugins`.
1966
+ * - Generates the root barrel file when `output.barrelType` is set.
1967
+ * - Writes all files through Fabric.
1968
+ *
1969
+ * Returns a {@link BuildOutput} even on failure — inspect `error` and
1970
+ * `failedPlugins` to determine whether the build succeeded.
1971
+ */
1619
1972
  async function safeBuild(options, overrides) {
1620
- const { fabric, pluginManager, events, sources } = overrides ? overrides : await setup(options);
1973
+ const { fabric, driver, events, sources } = overrides ? overrides : await setup(options);
1621
1974
  const failedPlugins = /* @__PURE__ */ new Set();
1622
1975
  const pluginTimings = /* @__PURE__ */ new Map();
1623
- const config = pluginManager.config;
1976
+ const config = driver.config;
1624
1977
  try {
1625
- for (const plugin of pluginManager.plugins) {
1626
- const context = pluginManager.getContext(plugin);
1978
+ for (const plugin of driver.plugins.values()) {
1979
+ const context = driver.getContext(plugin);
1627
1980
  const hrStart = process.hrtime();
1628
- const installer = plugin.install.bind(context);
1981
+ const { output } = plugin.options ?? {};
1982
+ const root = resolve(config.root, config.output.path);
1629
1983
  try {
1630
1984
  const timestamp = /* @__PURE__ */ new Date();
1631
1985
  await events.emit("plugin:start", plugin);
1632
1986
  await events.emit("debug", {
1633
1987
  date: timestamp,
1634
- logs: ["Installing plugin...", ` • Plugin Name: ${plugin.name}`]
1988
+ logs: ["Starting plugin...", ` • Plugin Name: ${plugin.name}`]
1635
1989
  });
1636
- await installer(context);
1990
+ await plugin.buildStart.call(context);
1991
+ if (plugin.schema || plugin.operation || plugin.operations) await runPluginAstHooks(plugin, context);
1992
+ if (output) {
1993
+ const barrelFiles = await getBarrelFiles(fabric.files, {
1994
+ type: output.barrelType ?? "named",
1995
+ root,
1996
+ output,
1997
+ meta: { pluginName: plugin.name }
1998
+ });
1999
+ await context.upsertFile(...barrelFiles);
2000
+ }
1637
2001
  const duration = getElapsedMs(hrStart);
1638
2002
  pluginTimings.set(plugin.name, duration);
1639
2003
  await events.emit("plugin:end", plugin, {
@@ -1642,7 +2006,7 @@ async function safeBuild(options, overrides) {
1642
2006
  });
1643
2007
  await events.emit("debug", {
1644
2008
  date: /* @__PURE__ */ new Date(),
1645
- logs: [`✓ Plugin installed successfully (${formatMs(duration)})`]
2009
+ logs: [`✓ Plugin started successfully (${formatMs(duration)})`]
1646
2010
  });
1647
2011
  } catch (caughtError) {
1648
2012
  const error = caughtError;
@@ -1656,7 +2020,7 @@ async function safeBuild(options, overrides) {
1656
2020
  await events.emit("debug", {
1657
2021
  date: errorTimestamp,
1658
2022
  logs: [
1659
- "✗ Plugin installation failed",
2023
+ "✗ Plugin start failed",
1660
2024
  ` • Plugin Name: ${plugin.name}`,
1661
2025
  ` • Error: ${error.constructor.name} - ${error.message}`,
1662
2026
  " • Stack Trace:",
@@ -1696,7 +2060,7 @@ async function safeBuild(options, overrides) {
1696
2060
  rootDir,
1697
2061
  existingExports: new Set(existingBarrel?.exports?.flatMap((e) => Array.isArray(e.name) ? e.name : [e.name]).filter((n) => Boolean(n)) ?? []),
1698
2062
  config,
1699
- pluginManager
2063
+ driver
1700
2064
  }),
1701
2065
  sources: [],
1702
2066
  imports: [],
@@ -1710,11 +2074,15 @@ async function safeBuild(options, overrides) {
1710
2074
  }
1711
2075
  const files = [...fabric.files];
1712
2076
  await fabric.write({ extension: config.output.extension });
2077
+ for (const plugin of driver.plugins.values()) if (plugin.buildEnd) {
2078
+ const context = driver.getContext(plugin);
2079
+ await plugin.buildEnd.call(context);
2080
+ }
1713
2081
  return {
1714
2082
  failedPlugins,
1715
2083
  fabric,
1716
2084
  files,
1717
- pluginManager,
2085
+ driver,
1718
2086
  pluginTimings,
1719
2087
  sources
1720
2088
  };
@@ -1723,16 +2091,16 @@ async function safeBuild(options, overrides) {
1723
2091
  failedPlugins,
1724
2092
  fabric,
1725
2093
  files: [],
1726
- pluginManager,
2094
+ driver,
1727
2095
  pluginTimings,
1728
2096
  error,
1729
2097
  sources
1730
2098
  };
1731
2099
  }
1732
2100
  }
1733
- function buildBarrelExports({ barrelFiles, rootDir, existingExports, config, pluginManager }) {
2101
+ function buildBarrelExports({ barrelFiles, rootDir, existingExports, config, driver }) {
1734
2102
  const pluginNameMap = /* @__PURE__ */ new Map();
1735
- for (const plugin of pluginManager.plugins) pluginNameMap.set(plugin.name, plugin);
2103
+ for (const plugin of driver.plugins.values()) pluginNameMap.set(plugin.name, plugin);
1736
2104
  return barrelFiles.flatMap((file) => {
1737
2105
  const containsOnlyTypes = file.sources?.every((source) => source.isTypeOnly);
1738
2106
  return (file.sources ?? []).flatMap((source) => {
@@ -1757,133 +2125,497 @@ function buildBarrelExports({ barrelFiles, rootDir, existingExports, config, plu
1757
2125
  function inputToAdapterSource(config) {
1758
2126
  if (Array.isArray(config.input)) return {
1759
2127
  type: "paths",
1760
- paths: config.input.map((i) => resolve(config.root, i.path))
2128
+ paths: config.input.map((i) => new URLPath(i.path).isURL ? i.path : resolve(config.root, i.path))
1761
2129
  };
1762
2130
  if ("data" in config.input) return {
1763
2131
  type: "data",
1764
2132
  data: config.input.data
1765
2133
  };
2134
+ if (new URLPath(config.input.path).isURL) return {
2135
+ type: "path",
2136
+ path: config.input.path
2137
+ };
1766
2138
  return {
1767
2139
  type: "path",
1768
2140
  path: resolve(config.root, config.input.path)
1769
2141
  };
1770
2142
  }
1771
2143
  //#endregion
1772
- //#region src/defineAdapter.ts
2144
+ //#region src/createAdapter.ts
1773
2145
  /**
1774
- * Wraps an adapter builder to make the options parameter optional.
2146
+ * Creates an adapter factory. Call the returned function with optional options to get the adapter instance.
1775
2147
  *
1776
2148
  * @example
1777
- * ```ts
1778
- * export const adapterOas = defineAdapter<OasAdapter>((options) => {
1779
- * const { validate = true, dateType = 'string' } = options
2149
+ * export const myAdapter = createAdapter<MyAdapter>((options) => {
1780
2150
  * return {
1781
- * name: adapterOasName,
1782
- * options: { validate, dateType, ... },
1783
- * parse(source) { ... },
2151
+ * name: 'my-adapter',
2152
+ * options,
2153
+ * async parse(source) { ... },
1784
2154
  * }
1785
2155
  * })
2156
+ *
2157
+ * // instantiate
2158
+ * const adapter = myAdapter({ validate: true })
2159
+ */
2160
+ function createAdapter(build) {
2161
+ return (options) => build(options ?? {});
2162
+ }
2163
+ //#endregion
2164
+ //#region src/createPlugin.ts
2165
+ /**
2166
+ * Creates a plugin factory. Call the returned function with optional options to get the plugin instance.
2167
+ *
2168
+ * @example
2169
+ * ```ts
2170
+ * export const myPlugin = createPlugin<MyPlugin>((options) => {
2171
+ * return {
2172
+ * name: 'my-plugin',
2173
+ * get options() { return options },
2174
+ * resolvePath(baseName) { ... },
2175
+ * resolveName(name, type) { ... },
2176
+ * }
2177
+ * })
2178
+ *
2179
+ * // instantiate
2180
+ * const plugin = myPlugin({ output: { path: 'src/gen' } })
1786
2181
  * ```
1787
2182
  */
1788
- function defineAdapter(build) {
2183
+ function createPlugin(build) {
1789
2184
  return (options) => build(options ?? {});
1790
2185
  }
1791
2186
  //#endregion
2187
+ //#region src/defineConfig.ts
2188
+ function defineConfig(config) {
2189
+ return config;
2190
+ }
2191
+ //#endregion
2192
+ //#region src/defineGenerator.ts
2193
+ /**
2194
+ * Defines a generator. Returns the object as-is with correct `this` typings.
2195
+ * No type discrimination (`type: 'react' | 'core'`) needed — `applyHookResult`
2196
+ * handles React elements and `File[]` uniformly.
2197
+ */
2198
+ function defineGenerator(generator) {
2199
+ return generator;
2200
+ }
2201
+ /**
2202
+ * Merges an array of generators into a single generator.
2203
+ *
2204
+ * The merged generator's `schema`, `operation`, and `operations` methods run
2205
+ * the corresponding method from each input generator in sequence, applying each
2206
+ * result via `applyHookResult`. This eliminates the need to write the loop
2207
+ * manually in each plugin.
2208
+ *
2209
+ * @param generators - Array of generators to merge into a single generator.
2210
+ *
2211
+ * @example
2212
+ * ```ts
2213
+ * const merged = mergeGenerators(generators)
2214
+ *
2215
+ * return {
2216
+ * name: pluginName,
2217
+ * schema: merged.schema,
2218
+ * operation: merged.operation,
2219
+ * operations: merged.operations,
2220
+ * }
2221
+ * ```
2222
+ */
2223
+ function mergeGenerators(generators) {
2224
+ return {
2225
+ name: generators.length > 0 ? generators.map((g) => g.name).join("+") : "merged",
2226
+ async schema(node, options) {
2227
+ for (const gen of generators) {
2228
+ if (!gen.schema) continue;
2229
+ await applyHookResult(await gen.schema.call(this, node, options), this.fabric);
2230
+ }
2231
+ },
2232
+ async operation(node, options) {
2233
+ for (const gen of generators) {
2234
+ if (!gen.operation) continue;
2235
+ await applyHookResult(await gen.operation.call(this, node, options), this.fabric);
2236
+ }
2237
+ },
2238
+ async operations(nodes, options) {
2239
+ for (const gen of generators) {
2240
+ if (!gen.operations) continue;
2241
+ await applyHookResult(await gen.operations.call(this, nodes, options), this.fabric);
2242
+ }
2243
+ }
2244
+ };
2245
+ }
2246
+ //#endregion
1792
2247
  //#region src/defineLogger.ts
2248
+ /**
2249
+ * Wraps a logger definition into a typed {@link Logger}.
2250
+ *
2251
+ * @example
2252
+ * export const myLogger = defineLogger({
2253
+ * name: 'my-logger',
2254
+ * install(context, options) {
2255
+ * context.on('info', (message) => console.log('ℹ', message))
2256
+ * context.on('error', (error) => console.error('✗', error.message))
2257
+ * },
2258
+ * })
2259
+ */
1793
2260
  function defineLogger(logger) {
1794
- return { ...logger };
2261
+ return logger;
1795
2262
  }
1796
2263
  //#endregion
1797
- //#region src/definePlugin.ts
2264
+ //#region src/definePresets.ts
1798
2265
  /**
1799
- * Wraps a plugin builder to make the options parameter optional.
2266
+ * Creates a typed presets registry object a named collection of {@link Preset} entries.
2267
+ *
2268
+ * @example
2269
+ * import { definePreset, definePresets } from '@kubb/core'
2270
+ * import { resolverTsLegacy } from '@kubb/plugin-ts'
2271
+ *
2272
+ * export const myPresets = definePresets({
2273
+ * kubbV4: definePreset('kubbV4', { resolvers: [resolverTsLegacy] }),
2274
+ * })
1800
2275
  */
1801
- function definePlugin(build) {
1802
- return (options) => build(options ?? {});
2276
+ function definePresets(presets) {
2277
+ return presets;
1803
2278
  }
1804
2279
  //#endregion
1805
- //#region src/PackageManager.ts
1806
- var PackageManager = class PackageManager {
1807
- static #cache = {};
1808
- #cwd;
1809
- constructor(workspace) {
1810
- if (workspace) this.#cwd = workspace;
1811
- }
1812
- set workspace(workspace) {
1813
- this.#cwd = workspace;
1814
- }
1815
- get workspace() {
1816
- return this.#cwd;
1817
- }
1818
- normalizeDirectory(directory) {
1819
- const lastChar = directory[directory.length - 1];
1820
- if (lastChar && !PATH_SEPARATORS.includes(lastChar)) return `${directory}/`;
1821
- return directory;
1822
- }
1823
- getLocation(path) {
1824
- let location = path;
1825
- if (this.#cwd) location = mod.createRequire(this.normalizeDirectory(this.#cwd)).resolve(path);
1826
- return location;
1827
- }
1828
- async import(path) {
1829
- let location = this.getLocation(path);
1830
- if (os.platform() === "win32") location = pathToFileURL(location).href;
1831
- const module = await import(location);
1832
- return module?.default ?? module;
1833
- }
1834
- async getPackageJSON() {
1835
- const pkgPath = pkg.up({ cwd: this.#cwd });
1836
- if (!pkgPath) return;
1837
- const json = await read(pkgPath);
1838
- return JSON.parse(json);
1839
- }
1840
- getPackageJSONSync() {
1841
- const pkgPath = pkg.up({ cwd: this.#cwd });
1842
- if (!pkgPath) return;
1843
- const json = readSync(pkgPath);
1844
- return JSON.parse(json);
1845
- }
1846
- static setVersion(dependency, version) {
1847
- PackageManager.#cache[dependency] = version;
1848
- }
1849
- #match(packageJSON, dependency) {
1850
- const dependencies = {
1851
- ...packageJSON.dependencies || {},
1852
- ...packageJSON.devDependencies || {}
2280
+ //#region src/defineResolver.ts
2281
+ /**
2282
+ * Checks if an operation matches a pattern for a given filter type (`tag`, `operationId`, `path`, `method`).
2283
+ */
2284
+ function matchesOperationPattern(node, type, pattern) {
2285
+ switch (type) {
2286
+ case "tag": return node.tags.some((tag) => !!tag.match(pattern));
2287
+ case "operationId": return !!node.operationId.match(pattern);
2288
+ case "path": return !!node.path.match(pattern);
2289
+ case "method": return !!node.method.toLowerCase().match(pattern);
2290
+ case "contentType": return !!node.requestBody?.contentType?.match(pattern);
2291
+ default: return false;
2292
+ }
2293
+ }
2294
+ /**
2295
+ * Checks if a schema matches a pattern for a given filter type (`schemaName`).
2296
+ *
2297
+ * Returns `null` when the filter type doesn't apply to schemas.
2298
+ */
2299
+ function matchesSchemaPattern(node, type, pattern) {
2300
+ switch (type) {
2301
+ case "schemaName": return node.name ? !!node.name.match(pattern) : false;
2302
+ default: return null;
2303
+ }
2304
+ }
2305
+ /**
2306
+ * Default name resolver used by `defineResolver`.
2307
+ *
2308
+ * - `camelCase` for `function` and `file` types.
2309
+ * - `PascalCase` for `type`.
2310
+ * - `camelCase` for everything else.
2311
+ */
2312
+ function defaultResolver(name, type) {
2313
+ let resolvedName = camelCase(name);
2314
+ if (type === "file" || type === "function") resolvedName = camelCase(name, { isFile: type === "file" });
2315
+ if (type === "type") resolvedName = pascalCase(name);
2316
+ return resolvedName;
2317
+ }
2318
+ /**
2319
+ * Default option resolver — applies include/exclude filters and merges matching override options.
2320
+ *
2321
+ * Returns `null` when the node is filtered out by an `exclude` rule or not matched by any `include` rule.
2322
+ *
2323
+ * @example Include/exclude filtering
2324
+ * ```ts
2325
+ * const options = defaultResolveOptions(operationNode, {
2326
+ * options: { output: 'types' },
2327
+ * exclude: [{ type: 'tag', pattern: 'internal' }],
2328
+ * })
2329
+ * // → null when node has tag 'internal'
2330
+ * ```
2331
+ *
2332
+ * @example Override merging
2333
+ * ```ts
2334
+ * const options = defaultResolveOptions(operationNode, {
2335
+ * options: { enumType: 'asConst' },
2336
+ * override: [{ type: 'operationId', pattern: 'listPets', options: { enumType: 'enum' } }],
2337
+ * })
2338
+ * // → { enumType: 'enum' } when operationId matches
2339
+ * ```
2340
+ */
2341
+ function defaultResolveOptions(node, { options, exclude = [], include, override = [] }) {
2342
+ if (isOperationNode(node)) {
2343
+ if (exclude.some(({ type, pattern }) => matchesOperationPattern(node, type, pattern))) return null;
2344
+ if (include && !include.some(({ type, pattern }) => matchesOperationPattern(node, type, pattern))) return null;
2345
+ const overrideOptions = override.find(({ type, pattern }) => matchesOperationPattern(node, type, pattern))?.options;
2346
+ return {
2347
+ ...options,
2348
+ ...overrideOptions
1853
2349
  };
1854
- if (typeof dependency === "string" && dependencies[dependency]) return dependencies[dependency];
1855
- const matchedDependency = Object.keys(dependencies).find((dep) => dep.match(dependency));
1856
- return matchedDependency ? dependencies[matchedDependency] : void 0;
1857
- }
1858
- async getVersion(dependency) {
1859
- if (typeof dependency === "string" && PackageManager.#cache[dependency]) return PackageManager.#cache[dependency];
1860
- const packageJSON = await this.getPackageJSON();
1861
- if (!packageJSON) return;
1862
- return this.#match(packageJSON, dependency);
1863
- }
1864
- getVersionSync(dependency) {
1865
- if (typeof dependency === "string" && PackageManager.#cache[dependency]) return PackageManager.#cache[dependency];
1866
- const packageJSON = this.getPackageJSONSync();
1867
- if (!packageJSON) return;
1868
- return this.#match(packageJSON, dependency);
1869
- }
1870
- async isValid(dependency, version) {
1871
- const packageVersion = await this.getVersion(dependency);
1872
- if (!packageVersion) return false;
1873
- if (packageVersion === version) return true;
1874
- const semVer = coerce(packageVersion);
1875
- if (!semVer) return false;
1876
- return satisfies(semVer, version);
1877
- }
1878
- isValidSync(dependency, version) {
1879
- const packageVersion = this.getVersionSync(dependency);
1880
- if (!packageVersion) return false;
1881
- if (packageVersion === version) return true;
1882
- const semVer = coerce(packageVersion);
1883
- if (!semVer) return false;
1884
- return satisfies(semVer, version);
1885
2350
  }
1886
- };
2351
+ if (isSchemaNode(node)) {
2352
+ if (exclude.some(({ type, pattern }) => matchesSchemaPattern(node, type, pattern) === true)) return null;
2353
+ if (include) {
2354
+ const applicable = include.map(({ type, pattern }) => matchesSchemaPattern(node, type, pattern)).filter((r) => r !== null);
2355
+ if (applicable.length > 0 && !applicable.includes(true)) return null;
2356
+ }
2357
+ const overrideOptions = override.find(({ type, pattern }) => matchesSchemaPattern(node, type, pattern) === true)?.options;
2358
+ return {
2359
+ ...options,
2360
+ ...overrideOptions
2361
+ };
2362
+ }
2363
+ return options;
2364
+ }
2365
+ /**
2366
+ * Default path resolver used by `defineResolver`.
2367
+ *
2368
+ * - Returns the output directory in `single` mode.
2369
+ * - Resolves into a tag- or path-based subdirectory when `group` and a `tag`/`path` value are provided.
2370
+ * - Falls back to a flat `output/baseName` path otherwise.
2371
+ *
2372
+ * A custom `group.name` function overrides the default subdirectory naming.
2373
+ * For `tag` groups the default is `${camelCase(tag)}Controller`.
2374
+ * For `path` groups the default is the first path segment after `/`.
2375
+ *
2376
+ * @example Flat output
2377
+ * ```ts
2378
+ * defaultResolvePath({ baseName: 'petTypes.ts' }, { root: '/src', output: { path: 'types' } })
2379
+ * // → '/src/types/petTypes.ts'
2380
+ * ```
2381
+ *
2382
+ * @example Tag-based grouping
2383
+ * ```ts
2384
+ * defaultResolvePath(
2385
+ * { baseName: 'petTypes.ts', tag: 'pets' },
2386
+ * { root: '/src', output: { path: 'types' }, group: { type: 'tag' } },
2387
+ * )
2388
+ * // → '/src/types/petsController/petTypes.ts'
2389
+ * ```
2390
+ *
2391
+ * @example Path-based grouping
2392
+ * ```ts
2393
+ * defaultResolvePath(
2394
+ * { baseName: 'petTypes.ts', path: '/pets/list' },
2395
+ * { root: '/src', output: { path: 'types' }, group: { type: 'path' } },
2396
+ * )
2397
+ * // → '/src/types/pets/petTypes.ts'
2398
+ * ```
2399
+ *
2400
+ * @example Single-file mode
2401
+ * ```ts
2402
+ * defaultResolvePath(
2403
+ * { baseName: 'petTypes.ts', pathMode: 'single' },
2404
+ * { root: '/src', output: { path: 'types' } },
2405
+ * )
2406
+ * // → '/src/types'
2407
+ * ```
2408
+ */
2409
+ function defaultResolvePath({ baseName, pathMode, tag, path: groupPath }, { root, output, group }) {
2410
+ if ((pathMode ?? getMode(path.resolve(root, output.path))) === "single") return path.resolve(root, output.path);
2411
+ if (group && (groupPath || tag)) return path.resolve(root, output.path, group.name({ group: group.type === "path" ? groupPath : tag }), baseName);
2412
+ return path.resolve(root, output.path, baseName);
2413
+ }
2414
+ /**
2415
+ * Default file resolver used by `defineResolver`.
2416
+ *
2417
+ * Resolves a `KubbFile.File` by combining name resolution (`resolver.default`) with
2418
+ * path resolution (`resolver.resolvePath`). The resolved file always has empty
2419
+ * `sources`, `imports`, and `exports` arrays — consumers populate those separately.
2420
+ *
2421
+ * In `single` mode the name is omitted and the file sits directly in the output directory.
2422
+ *
2423
+ * @example Resolve a schema file
2424
+ * ```ts
2425
+ * const file = defaultResolveFile.call(resolver,
2426
+ * { name: 'pet', extname: '.ts' },
2427
+ * { root: '/src', output: { path: 'types' } },
2428
+ * )
2429
+ * // → { baseName: 'pet.ts', path: '/src/types/pet.ts', sources: [], ... }
2430
+ * ```
2431
+ *
2432
+ * @example Resolve an operation file with tag grouping
2433
+ * ```ts
2434
+ * const file = defaultResolveFile.call(resolver,
2435
+ * { name: 'listPets', extname: '.ts', tag: 'pets' },
2436
+ * { root: '/src', output: { path: 'types' }, group: { type: 'tag' } },
2437
+ * )
2438
+ * // → { baseName: 'listPets.ts', path: '/src/types/petsController/listPets.ts', ... }
2439
+ * ```
2440
+ */
2441
+ function defaultResolveFile({ name, extname, tag, path: groupPath }, context) {
2442
+ const pathMode = getMode(path.resolve(context.root, context.output.path));
2443
+ const baseName = `${pathMode === "single" ? "" : this.default(name, "file")}${extname}`;
2444
+ const filePath = this.resolvePath({
2445
+ baseName,
2446
+ pathMode,
2447
+ tag,
2448
+ path: groupPath
2449
+ }, context);
2450
+ return {
2451
+ path: filePath,
2452
+ baseName: path.basename(filePath),
2453
+ meta: { pluginName: this.pluginName },
2454
+ sources: [],
2455
+ imports: [],
2456
+ exports: []
2457
+ };
2458
+ }
2459
+ /**
2460
+ * Generates the default "Generated by Kubb" banner from config and optional node metadata.
2461
+ */
2462
+ function buildDefaultBanner({ title, description, version, config }) {
2463
+ try {
2464
+ let source = "";
2465
+ if (Array.isArray(config.input)) {
2466
+ const first = config.input[0];
2467
+ if (first && "path" in first) source = path.basename(first.path);
2468
+ } else if ("path" in config.input) source = path.basename(config.input.path);
2469
+ else if ("data" in config.input) source = "text content";
2470
+ let banner = "/**\n* Generated by Kubb (https://kubb.dev/).\n* Do not edit manually.\n";
2471
+ if (config.output.defaultBanner === "simple") {
2472
+ banner += "*/\n";
2473
+ return banner;
2474
+ }
2475
+ if (source) banner += `* Source: ${source}\n`;
2476
+ if (title) banner += `* Title: ${title}\n`;
2477
+ if (description) {
2478
+ const formattedDescription = description.replace(/\n/gm, "\n* ");
2479
+ banner += `* Description: ${formattedDescription}\n`;
2480
+ }
2481
+ if (version) banner += `* OpenAPI spec version: ${version}\n`;
2482
+ banner += "*/\n";
2483
+ return banner;
2484
+ } catch (_error) {
2485
+ return "/**\n* Generated by Kubb (https://kubb.dev/).\n* Do not edit manually.\n*/";
2486
+ }
2487
+ }
2488
+ /**
2489
+ * Default banner resolver — returns the banner string for a generated file.
2490
+ *
2491
+ * A user-supplied `output.banner` overrides the default Kubb "Generated by Kubb" notice.
2492
+ * When no `output.banner` is set, the Kubb notice is used (including `title` and `version`
2493
+ * from the OAS spec when a `node` is provided).
2494
+ *
2495
+ * - When `output.banner` is a function and `node` is provided, returns `output.banner(node)`.
2496
+ * - When `output.banner` is a function and `node` is absent, falls back to the Kubb notice.
2497
+ * - When `output.banner` is a string, returns it directly.
2498
+ * - When `config.output.defaultBanner` is `false`, returns `undefined`.
2499
+ * - Otherwise returns the Kubb "Generated by Kubb" notice.
2500
+ *
2501
+ * @example String banner overrides default
2502
+ * ```ts
2503
+ * defaultResolveBanner(undefined, { output: { banner: '// my banner' }, config })
2504
+ * // → '// my banner'
2505
+ * ```
2506
+ *
2507
+ * @example Function banner with node
2508
+ * ```ts
2509
+ * defaultResolveBanner(rootNode, { output: { banner: (node) => `// v${node.version}` }, config })
2510
+ * // → '// v3.0.0'
2511
+ * ```
2512
+ *
2513
+ * @example No user banner — Kubb notice with OAS metadata
2514
+ * ```ts
2515
+ * defaultResolveBanner(rootNode, { config })
2516
+ * // → '/** Generated by Kubb ... Title: Pet Store ... *\/'
2517
+ * ```
2518
+ *
2519
+ * @example Disabled default banner
2520
+ * ```ts
2521
+ * defaultResolveBanner(undefined, { config: { output: { defaultBanner: false }, ...config } })
2522
+ * // → undefined
2523
+ * ```
2524
+ */
2525
+ function defaultResolveBanner(node, { output, config }) {
2526
+ if (typeof output?.banner === "function") return output.banner(node);
2527
+ if (typeof output?.banner === "string") return output.banner;
2528
+ if (config.output.defaultBanner === false) return;
2529
+ return buildDefaultBanner({
2530
+ title: node?.meta?.title,
2531
+ version: node?.meta?.version,
2532
+ config
2533
+ });
2534
+ }
2535
+ /**
2536
+ * Default footer resolver — returns the footer string for a generated file.
2537
+ *
2538
+ * - When `output.footer` is a function and `node` is provided, calls it with the node.
2539
+ * - When `output.footer` is a function and `node` is absent, returns `undefined`.
2540
+ * - When `output.footer` is a string, returns it directly.
2541
+ * - Otherwise returns `undefined`.
2542
+ *
2543
+ * @example String footer
2544
+ * ```ts
2545
+ * defaultResolveFooter(undefined, { output: { footer: '// end of file' }, config })
2546
+ * // → '// end of file'
2547
+ * ```
2548
+ *
2549
+ * @example Function footer with node
2550
+ * ```ts
2551
+ * defaultResolveFooter(rootNode, { output: { footer: (node) => `// ${node.title}` }, config })
2552
+ * // → '// Pet Store'
2553
+ * ```
2554
+ */
2555
+ function defaultResolveFooter(node, { output }) {
2556
+ if (typeof output?.footer === "function") return node ? output.footer(node) : void 0;
2557
+ if (typeof output?.footer === "string") return output.footer;
2558
+ }
2559
+ /**
2560
+ * Defines a resolver for a plugin, injecting built-in defaults for name casing,
2561
+ * include/exclude/override filtering, path resolution, and file construction.
2562
+ *
2563
+ * All four defaults can be overridden by providing them in the builder function:
2564
+ * - `default` — name casing strategy (camelCase / PascalCase)
2565
+ * - `resolveOptions` — include/exclude/override filtering
2566
+ * - `resolvePath` — output path computation
2567
+ * - `resolveFile` — full `KubbFile.File` construction
2568
+ *
2569
+ * Methods in the builder have access to `this` (the full resolver object), so they
2570
+ * can call other resolver methods without circular imports.
2571
+ *
2572
+ * @example Basic resolver with naming helpers
2573
+ * ```ts
2574
+ * export const resolver = defineResolver<PluginTs>(() => ({
2575
+ * name: 'default',
2576
+ * resolveName(node) {
2577
+ * return this.default(node.name, 'function')
2578
+ * },
2579
+ * resolveTypedName(node) {
2580
+ * return this.default(node.name, 'type')
2581
+ * },
2582
+ * }))
2583
+ * ```
2584
+ *
2585
+ * @example Override resolvePath for a custom output structure
2586
+ * ```ts
2587
+ * export const resolver = defineResolver<PluginTs>(() => ({
2588
+ * name: 'custom',
2589
+ * resolvePath({ baseName }, { root, output }) {
2590
+ * return path.resolve(root, output.path, 'generated', baseName)
2591
+ * },
2592
+ * }))
2593
+ * ```
2594
+ *
2595
+ * @example Use this.default inside a helper
2596
+ * ```ts
2597
+ * export const resolver = defineResolver<PluginTs>(() => ({
2598
+ * name: 'default',
2599
+ * resolveParamName(node, param) {
2600
+ * return this.default(`${node.operationId} ${param.in} ${param.name}`, 'type')
2601
+ * },
2602
+ * }))
2603
+ * ```
2604
+ */
2605
+ function defineResolver(build) {
2606
+ return {
2607
+ default: defaultResolver,
2608
+ resolveOptions: defaultResolveOptions,
2609
+ resolvePath: defaultResolvePath,
2610
+ resolveFile: defaultResolveFile,
2611
+ resolveBanner: defaultResolveBanner,
2612
+ resolveFooter: defaultResolveFooter,
2613
+ ...build()
2614
+ };
2615
+ }
2616
+ //#endregion
2617
+ //#region src/KubbFile.ts
2618
+ var KubbFile_exports = /* @__PURE__ */ __exportAll({});
1887
2619
  //#endregion
1888
2620
  //#region src/storages/memoryStorage.ts
1889
2621
  /**
@@ -1903,7 +2635,7 @@ var PackageManager = class PackageManager {
1903
2635
  * })
1904
2636
  * ```
1905
2637
  */
1906
- const memoryStorage = defineStorage(() => {
2638
+ const memoryStorage = createStorage(() => {
1907
2639
  const store = /* @__PURE__ */ new Map();
1908
2640
  return {
1909
2641
  name: "memory",
@@ -1935,7 +2667,7 @@ const memoryStorage = defineStorage(() => {
1935
2667
  //#endregion
1936
2668
  //#region src/utils/FunctionParams.ts
1937
2669
  /**
1938
- * @deprecated
2670
+ * @deprecated use ast package instead
1939
2671
  */
1940
2672
  var FunctionParams = class FunctionParams {
1941
2673
  #items = [];
@@ -2011,14 +2743,10 @@ var FunctionParams = class FunctionParams {
2011
2743
  //#endregion
2012
2744
  //#region src/utils/formatters.ts
2013
2745
  /**
2014
- * Check if a formatter command is available in the system.
2015
- *
2016
- * @param formatter - The formatter to check ('biome', 'prettier', or 'oxfmt')
2017
- * @returns Promise that resolves to true if the formatter is available, false otherwise
2746
+ * Returns `true` when the given formatter is installed and callable.
2018
2747
  *
2019
- * @remarks
2020
- * This function checks availability by running `<formatter> --version` command.
2021
- * All supported formatters (biome, prettier, oxfmt) implement the --version flag.
2748
+ * Availability is detected by running `<formatter> --version` and checking
2749
+ * that the process exits without error.
2022
2750
  */
2023
2751
  async function isFormatterAvailable(formatter) {
2024
2752
  try {
@@ -2029,263 +2757,93 @@ async function isFormatterAvailable(formatter) {
2029
2757
  }
2030
2758
  }
2031
2759
  /**
2032
- * Detect which formatter is available in the system.
2760
+ * Detects the first available code formatter on the current system.
2033
2761
  *
2034
- * @returns Promise that resolves to the first available formatter or undefined if none are found
2035
- *
2036
- * @remarks
2037
- * Checks in order of preference: biome, oxfmt, prettier.
2038
- * Uses the `--version` flag to detect if each formatter command is available.
2039
- * This is a reliable method as all supported formatters implement this flag.
2762
+ * - Checks in preference order: `biome`, `oxfmt`, `prettier`.
2763
+ * - Returns `null` when none are found.
2040
2764
  *
2041
2765
  * @example
2042
- * ```typescript
2766
+ * ```ts
2043
2767
  * const formatter = await detectFormatter()
2044
2768
  * if (formatter) {
2045
2769
  * console.log(`Using ${formatter} for formatting`)
2046
- * } else {
2047
- * console.log('No formatter found')
2048
2770
  * }
2049
2771
  * ```
2050
2772
  */
2051
2773
  async function detectFormatter() {
2052
- for (const formatter of [
2774
+ const formatterNames = new Set([
2053
2775
  "biome",
2054
2776
  "oxfmt",
2055
2777
  "prettier"
2056
- ]) if (await isFormatterAvailable(formatter)) return formatter;
2778
+ ]);
2779
+ for (const formatter of formatterNames) if (await isFormatterAvailable(formatter)) return formatter;
2780
+ return null;
2057
2781
  }
2058
2782
  //#endregion
2059
- //#region src/utils/TreeNode.ts
2060
- var TreeNode = class TreeNode {
2061
- data;
2062
- parent;
2063
- children = [];
2064
- #cachedLeaves = void 0;
2065
- constructor(data, parent) {
2066
- this.data = data;
2067
- this.parent = parent;
2068
- }
2069
- addChild(data) {
2070
- const child = new TreeNode(data, this);
2071
- if (!this.children) this.children = [];
2072
- this.children.push(child);
2073
- return child;
2074
- }
2075
- get root() {
2076
- if (!this.parent) return this;
2077
- return this.parent.root;
2078
- }
2079
- get leaves() {
2080
- if (!this.children || this.children.length === 0) return [this];
2081
- if (this.#cachedLeaves) return this.#cachedLeaves;
2082
- const leaves = [];
2083
- for (const child of this.children) leaves.push(...child.leaves);
2084
- this.#cachedLeaves = leaves;
2085
- return leaves;
2086
- }
2087
- forEach(callback) {
2088
- if (typeof callback !== "function") throw new TypeError("forEach() callback must be a function");
2089
- callback(this);
2090
- for (const child of this.children) child.forEach(callback);
2091
- return this;
2092
- }
2093
- findDeep(predicate) {
2094
- if (typeof predicate !== "function") throw new TypeError("find() predicate must be a function");
2095
- return this.leaves.find(predicate);
2096
- }
2097
- forEachDeep(callback) {
2098
- if (typeof callback !== "function") throw new TypeError("forEach() callback must be a function");
2099
- this.leaves.forEach(callback);
2100
- }
2101
- filterDeep(callback) {
2102
- if (typeof callback !== "function") throw new TypeError("filter() callback must be a function");
2103
- return this.leaves.filter(callback);
2104
- }
2105
- mapDeep(callback) {
2106
- if (typeof callback !== "function") throw new TypeError("map() callback must be a function");
2107
- return this.leaves.map(callback);
2108
- }
2109
- static build(files, root) {
2110
- try {
2111
- const filteredTree = buildDirectoryTree(files, root);
2112
- if (!filteredTree) return null;
2113
- const treeNode = new TreeNode({
2114
- name: filteredTree.name,
2115
- path: filteredTree.path,
2116
- file: filteredTree.file,
2117
- type: getMode(filteredTree.path)
2118
- });
2119
- const recurse = (node, item) => {
2120
- const subNode = node.addChild({
2121
- name: item.name,
2122
- path: item.path,
2123
- file: item.file,
2124
- type: getMode(item.path)
2125
- });
2126
- if (item.children?.length) item.children?.forEach((child) => {
2127
- recurse(subNode, child);
2128
- });
2129
- };
2130
- filteredTree.children?.forEach((child) => {
2131
- recurse(treeNode, child);
2132
- });
2133
- return treeNode;
2134
- } catch (error) {
2135
- throw new Error("Something went wrong with creating barrel files with the TreeNode class", { cause: error });
2136
- }
2137
- }
2138
- };
2139
- const normalizePath = (p) => p.replaceAll("\\", "/");
2140
- function buildDirectoryTree(files, rootFolder = "") {
2141
- const normalizedRootFolder = normalizePath(rootFolder);
2142
- const rootPrefix = normalizedRootFolder.endsWith("/") ? normalizedRootFolder : `${normalizedRootFolder}/`;
2143
- const filteredFiles = files.filter((file) => {
2144
- const normalizedFilePath = normalizePath(file.path);
2145
- return rootFolder ? normalizedFilePath.startsWith(rootPrefix) && !normalizedFilePath.endsWith(".json") : !normalizedFilePath.endsWith(".json");
2146
- });
2147
- if (filteredFiles.length === 0) return null;
2148
- const root = {
2149
- name: rootFolder || "",
2150
- path: rootFolder || "",
2151
- children: []
2152
- };
2153
- filteredFiles.forEach((file) => {
2154
- const parts = file.path.slice(rootFolder.length).split("/").filter(Boolean);
2155
- let currentLevel = root.children;
2156
- let currentPath = normalizePath(rootFolder);
2157
- parts.forEach((part, index) => {
2158
- currentPath = path.posix.join(currentPath, part);
2159
- let existingNode = currentLevel.find((node) => node.name === part);
2160
- if (!existingNode) {
2161
- if (index === parts.length - 1) existingNode = {
2162
- name: part,
2163
- file,
2164
- path: currentPath
2165
- };
2166
- else existingNode = {
2167
- name: part,
2168
- path: currentPath,
2169
- children: []
2170
- };
2171
- currentLevel.push(existingNode);
2172
- }
2173
- if (!existingNode.file) currentLevel = existingNode.children;
2174
- });
2175
- });
2176
- return root;
2177
- }
2178
- //#endregion
2179
- //#region src/BarrelManager.ts
2180
- /** biome-ignore-all lint/suspicious/useIterableCallbackReturn: not needed */
2181
- var BarrelManager = class {
2182
- getFiles({ files: generatedFiles, root }) {
2183
- const cachedFiles = /* @__PURE__ */ new Map();
2184
- TreeNode.build(generatedFiles, root)?.forEach((treeNode) => {
2185
- if (!treeNode || !treeNode.children || !treeNode.parent?.data.path) return;
2186
- const barrelFile = {
2187
- path: join(treeNode.parent?.data.path, "index.ts"),
2188
- baseName: "index.ts",
2189
- exports: [],
2190
- imports: [],
2191
- sources: []
2192
- };
2193
- const previousBarrelFile = cachedFiles.get(barrelFile.path);
2194
- treeNode.leaves.forEach((item) => {
2195
- if (!item.data.name) return;
2196
- (item.data.file?.sources || []).forEach((source) => {
2197
- if (!item.data.file?.path || !source.isIndexable || !source.name) return;
2198
- if (previousBarrelFile?.sources.some((item) => item.name === source.name && item.isTypeOnly === source.isTypeOnly)) return;
2199
- barrelFile.exports.push({
2200
- name: [source.name],
2201
- path: getRelativePath(treeNode.parent?.data.path, item.data.path),
2202
- isTypeOnly: source.isTypeOnly
2203
- });
2204
- barrelFile.sources.push({
2205
- name: source.name,
2206
- isTypeOnly: source.isTypeOnly,
2207
- value: "",
2208
- isExportable: false,
2209
- isIndexable: false
2210
- });
2211
- });
2212
- });
2213
- if (previousBarrelFile) {
2214
- previousBarrelFile.sources.push(...barrelFile.sources);
2215
- previousBarrelFile.exports?.push(...barrelFile.exports || []);
2216
- } else cachedFiles.set(barrelFile.path, barrelFile);
2217
- });
2218
- return [...cachedFiles.values()];
2219
- }
2220
- };
2221
- //#endregion
2222
- //#region src/utils/getBarrelFiles.ts
2223
- function trimExtName(text) {
2224
- const dotIndex = text.lastIndexOf(".");
2225
- if (dotIndex > 0 && !text.includes("/", dotIndex)) return text.slice(0, dotIndex);
2226
- return text;
2227
- }
2228
- async function getBarrelFiles(files, { type, meta = {}, root, output }) {
2229
- if (!type || type === "propagate") return [];
2230
- const barrelManager = new BarrelManager();
2231
- const pathToBuildFrom = join(root, output.path);
2232
- if (trimExtName(pathToBuildFrom).endsWith("index")) return [];
2233
- const barrelFiles = barrelManager.getFiles({
2234
- files,
2235
- root: pathToBuildFrom,
2236
- meta
2237
- });
2238
- if (type === "all") return barrelFiles.map((file) => {
2239
- return {
2240
- ...file,
2241
- exports: file.exports?.map((exportItem) => {
2242
- return {
2243
- ...exportItem,
2244
- name: void 0
2245
- };
2246
- })
2247
- };
2248
- });
2249
- return barrelFiles.map((indexFile) => {
2250
- return {
2251
- ...indexFile,
2252
- meta
2253
- };
2254
- });
2783
+ //#region src/utils/getConfigs.ts
2784
+ /**
2785
+ * Resolves a {@link ConfigInput} into a normalized array of {@link Config} objects.
2786
+ *
2787
+ * - Awaits the config when it is a `Promise`.
2788
+ * - Calls the factory function with `args` when the config is a function.
2789
+ * - Wraps a single config object in an array for uniform downstream handling.
2790
+ */
2791
+ async function getConfigs(config, args) {
2792
+ const resolved = await (typeof config === "function" ? config(args) : config);
2793
+ return (Array.isArray(resolved) ? resolved : [resolved]).map((item) => ({
2794
+ plugins: [],
2795
+ ...item
2796
+ }));
2255
2797
  }
2256
2798
  //#endregion
2257
- //#region src/utils/getPlugins.ts
2258
- function isJSONPlugins(plugins) {
2259
- return Array.isArray(plugins) && plugins.some((plugin) => Array.isArray(plugin) && typeof plugin[0] === "string");
2260
- }
2261
- function isObjectPlugins(plugins) {
2262
- return plugins instanceof Object && !Array.isArray(plugins);
2263
- }
2264
- function getPlugins(plugins) {
2265
- if (isObjectPlugins(plugins)) throw new Error("Object plugins are not supported anymore, best to use http://kubb.dev/getting-started/configure#json");
2266
- if (isJSONPlugins(plugins)) throw new Error("JSON plugins are not supported anymore, best to use http://kubb.dev/getting-started/configure#json");
2267
- return Promise.resolve(plugins);
2799
+ //#region src/utils/getPreset.ts
2800
+ /**
2801
+ * Returns a copy of `defaults` where each function in `userOverrides` is wrapped
2802
+ * so a `null`/`undefined` return falls back to the original. Non-function values
2803
+ * are assigned directly. All calls use the merged object as `this`.
2804
+ */
2805
+ function withFallback(defaults, userOverrides) {
2806
+ const merged = { ...defaults };
2807
+ for (const key of Object.keys(userOverrides)) {
2808
+ const userVal = userOverrides[key];
2809
+ const defaultVal = defaults[key];
2810
+ if (typeof userVal === "function" && typeof defaultVal === "function") merged[key] = (...args) => userVal.apply(merged, args) ?? defaultVal.apply(merged, args);
2811
+ else if (userVal !== void 0) merged[key] = userVal;
2812
+ }
2813
+ return merged;
2268
2814
  }
2269
- //#endregion
2270
- //#region src/utils/getConfigs.ts
2271
2815
  /**
2272
- * Converting UserConfig to Config Array without a change in the object beside the JSON convert.
2816
+ * Resolves a named preset into a resolver, transformer, and generators.
2817
+ *
2818
+ * - Selects the preset resolver; wraps it with user overrides using null/undefined fallback.
2819
+ * - Composes the preset's transformers into a single visitor; wraps it with the user transformer using null/undefined fallback.
2820
+ * - Combines preset generators with user-supplied generators; falls back to the `default` preset's generators when neither provides any.
2273
2821
  */
2274
- async function getConfigs(config, args) {
2275
- let userConfigs = await (typeof config === "function" ? Promise.resolve(config(args)) : Promise.resolve(config));
2276
- if (!Array.isArray(userConfigs)) userConfigs = [userConfigs];
2277
- const results = [];
2278
- for (const item of userConfigs) {
2279
- const plugins = item.plugins ? await getPlugins(item.plugins) : void 0;
2280
- results.push({
2281
- ...item,
2282
- plugins
2283
- });
2284
- }
2285
- return results;
2822
+ function getPreset(params) {
2823
+ const { preset: presetName, presets, resolver: userResolver, transformer: userTransformer, generators: userGenerators = [] } = params;
2824
+ const preset = presets[presetName];
2825
+ const presetResolver = preset?.resolver ?? presets["default"].resolver;
2826
+ const resolver = userResolver ? withFallback(presetResolver, userResolver) : presetResolver;
2827
+ const presetTransformers = preset?.transformers ?? [];
2828
+ const presetTransformer = presetTransformers.length > 0 ? composeTransformers$1(...presetTransformers) : void 0;
2829
+ const transformer = presetTransformer && userTransformer ? withFallback(presetTransformer, userTransformer) : userTransformer ?? presetTransformer;
2830
+ const presetGenerators = preset?.generators ?? [];
2831
+ const defaultGenerators = presets["default"]?.generators ?? [];
2832
+ return {
2833
+ resolver,
2834
+ transformer,
2835
+ generators: presetGenerators.length > 0 || userGenerators.length > 0 ? [...presetGenerators, ...userGenerators] : defaultGenerators,
2836
+ preset
2837
+ };
2286
2838
  }
2287
2839
  //#endregion
2288
2840
  //#region src/utils/linters.ts
2841
+ /**
2842
+ * Returns `true` when the given linter is installed and callable.
2843
+ *
2844
+ * Availability is detected by running `<linter> --version` and checking
2845
+ * that the process exits without error.
2846
+ */
2289
2847
  async function isLinterAvailable(linter) {
2290
2848
  try {
2291
2849
  await x(linter, ["--version"], { nodeOptions: { stdio: "ignore" } });
@@ -2294,14 +2852,73 @@ async function isLinterAvailable(linter) {
2294
2852
  return false;
2295
2853
  }
2296
2854
  }
2855
+ /**
2856
+ * Detects the first available linter on the current system.
2857
+ *
2858
+ * - Checks in preference order: `biome`, `oxlint`, `eslint`.
2859
+ * - Returns `null` when none are found.
2860
+ *
2861
+ * @example
2862
+ * ```ts
2863
+ * const linter = await detectLinter()
2864
+ * if (linter) {
2865
+ * console.log(`Using ${linter} for linting`)
2866
+ * }
2867
+ * ```
2868
+ */
2297
2869
  async function detectLinter() {
2298
- for (const linter of [
2870
+ const linterNames = new Set([
2299
2871
  "biome",
2300
2872
  "oxlint",
2301
2873
  "eslint"
2302
- ]) if (await isLinterAvailable(linter)) return linter;
2874
+ ]);
2875
+ for (const linter of linterNames) if (await isLinterAvailable(linter)) return linter;
2876
+ return null;
2877
+ }
2878
+ //#endregion
2879
+ //#region src/utils/packageJSON.ts
2880
+ function getPackageJSONSync(cwd) {
2881
+ const pkgPath = pkg.up({ cwd });
2882
+ if (!pkgPath) return null;
2883
+ return JSON.parse(readSync(pkgPath));
2884
+ }
2885
+ function match(packageJSON, dependency) {
2886
+ const dependencies = {
2887
+ ...packageJSON.dependencies || {},
2888
+ ...packageJSON.devDependencies || {}
2889
+ };
2890
+ if (typeof dependency === "string" && dependencies[dependency]) return dependencies[dependency];
2891
+ const matched = Object.keys(dependencies).find((dep) => dep.match(dependency));
2892
+ return matched ? dependencies[matched] ?? null : null;
2893
+ }
2894
+ function getVersionSync(dependency, cwd) {
2895
+ const packageJSON = getPackageJSONSync(cwd);
2896
+ return packageJSON ? match(packageJSON, dependency) : null;
2897
+ }
2898
+ /**
2899
+ * Returns `true` when the nearest `package.json` declares a dependency that
2900
+ * satisfies the given semver range.
2901
+ *
2902
+ * - Searches both `dependencies` and `devDependencies`.
2903
+ * - Accepts a string package name or a `RegExp` to match scoped/pattern packages.
2904
+ * - Uses `semver.satisfies` for range comparison; returns `false` when the
2905
+ * version string cannot be coerced into a valid semver.
2906
+ *
2907
+ * @example
2908
+ * ```ts
2909
+ * satisfiesDependency('react', '>=18') // true when react@18.x is installed
2910
+ * satisfiesDependency(/^@tanstack\//, '>=5') // true when any @tanstack/* >=5 is found
2911
+ * ```
2912
+ */
2913
+ function satisfiesDependency(dependency, version, cwd) {
2914
+ const packageVersion = getVersionSync(dependency, cwd);
2915
+ if (!packageVersion) return false;
2916
+ if (packageVersion === version) return true;
2917
+ const semVer = coerce(packageVersion);
2918
+ if (!semVer) return false;
2919
+ return satisfies(semVer, version);
2303
2920
  }
2304
2921
  //#endregion
2305
- 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 };
2922
+ export { AsyncEventEmitter, FunctionParams, KubbFile_exports as KubbFile, PluginDriver, URLPath, build, build as default, buildDefaultBanner, composeTransformers, createAdapter, createPlugin, createStorage, defaultResolveBanner, defaultResolveFile, defaultResolveFooter, defaultResolveOptions, defaultResolvePath, defineConfig, defineGenerator, defineLogger, defineParser, definePresets, definePrinter, defineResolver, detectFormatter, detectLinter, formatters, fsStorage, getBarrelFiles, getConfigs, getMode, getPreset, isInputPath, linters, logLevel, memoryStorage, mergeGenerators, safeBuild, satisfiesDependency, setup };
2306
2923
 
2307
2924
  //# sourceMappingURL=index.js.map