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

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