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