@kubb/core 5.0.0-alpha.7 → 5.0.0-alpha.70

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 (69) hide show
  1. package/README.md +3 -2
  2. package/dist/PluginDriver-BXibeQk-.cjs +1036 -0
  3. package/dist/PluginDriver-BXibeQk-.cjs.map +1 -0
  4. package/dist/PluginDriver-DV3p2Hky.js +945 -0
  5. package/dist/PluginDriver-DV3p2Hky.js.map +1 -0
  6. package/dist/index.cjs +730 -1773
  7. package/dist/index.cjs.map +1 -1
  8. package/dist/index.d.ts +289 -240
  9. package/dist/index.js +717 -1741
  10. package/dist/index.js.map +1 -1
  11. package/dist/mocks.cjs +139 -0
  12. package/dist/mocks.cjs.map +1 -0
  13. package/dist/mocks.d.ts +74 -0
  14. package/dist/mocks.js +134 -0
  15. package/dist/mocks.js.map +1 -0
  16. package/dist/types-DWtkW2RX.d.ts +1915 -0
  17. package/package.json +51 -57
  18. package/src/FileManager.ts +115 -0
  19. package/src/FileProcessor.ts +86 -0
  20. package/src/Kubb.ts +205 -130
  21. package/src/PluginDriver.ts +326 -565
  22. package/src/constants.ts +20 -47
  23. package/src/createAdapter.ts +25 -0
  24. package/src/createKubb.ts +546 -0
  25. package/src/createRenderer.ts +57 -0
  26. package/src/createStorage.ts +58 -0
  27. package/src/defineGenerator.ts +88 -100
  28. package/src/defineLogger.ts +13 -3
  29. package/src/defineMiddleware.ts +64 -0
  30. package/src/defineParser.ts +45 -0
  31. package/src/definePlugin.ts +78 -7
  32. package/src/defineResolver.ts +521 -0
  33. package/src/devtools.ts +14 -14
  34. package/src/index.ts +13 -16
  35. package/src/mocks.ts +172 -0
  36. package/src/renderNode.ts +35 -0
  37. package/src/storages/fsStorage.ts +43 -13
  38. package/src/storages/memoryStorage.ts +6 -4
  39. package/src/types.ts +752 -226
  40. package/src/utils/diagnostics.ts +4 -1
  41. package/src/utils/isInputPath.ts +10 -0
  42. package/src/utils/packageJSON.ts +99 -0
  43. package/dist/PluginDriver-Dma9KhLK.d.ts +0 -1056
  44. package/dist/chunk-ByKO4r7w.cjs +0 -38
  45. package/dist/hooks.cjs +0 -102
  46. package/dist/hooks.cjs.map +0 -1
  47. package/dist/hooks.d.ts +0 -75
  48. package/dist/hooks.js +0 -97
  49. package/dist/hooks.js.map +0 -1
  50. package/src/PackageManager.ts +0 -180
  51. package/src/build.ts +0 -419
  52. package/src/config.ts +0 -56
  53. package/src/defineAdapter.ts +0 -22
  54. package/src/defineStorage.ts +0 -56
  55. package/src/errors.ts +0 -1
  56. package/src/hooks/index.ts +0 -4
  57. package/src/hooks/useKubb.ts +0 -138
  58. package/src/hooks/useMode.ts +0 -11
  59. package/src/hooks/usePlugin.ts +0 -11
  60. package/src/hooks/usePluginDriver.ts +0 -11
  61. package/src/utils/FunctionParams.ts +0 -155
  62. package/src/utils/TreeNode.ts +0 -215
  63. package/src/utils/executeStrategies.ts +0 -81
  64. package/src/utils/formatters.ts +0 -56
  65. package/src/utils/getBarrelFiles.ts +0 -141
  66. package/src/utils/getConfigs.ts +0 -30
  67. package/src/utils/getPlugins.ts +0 -23
  68. package/src/utils/linters.ts +0 -25
  69. package/src/utils/resolveOptions.ts +0 -93
package/dist/index.js CHANGED
@@ -1,29 +1,20 @@
1
- import "./chunk--u3MIqq1.js";
2
- import mod from "node:module";
1
+ import { t as __name } from "./chunk--u3MIqq1.js";
2
+ import { a as DEFAULT_BANNER, c as logLevel, i as defineResolver, l as camelCase, n as applyHookResult, o as DEFAULT_EXTENSION, r as FileManager, s as DEFAULT_STUDIO_URL, t as PluginDriver } from "./PluginDriver-DV3p2Hky.js";
3
3
  import { EventEmitter } from "node:events";
4
- import { parseArgs, styleText } from "node:util";
5
- import { readFileSync } from "node:fs";
6
4
  import { access, mkdir, readFile, readdir, rm, writeFile } from "node:fs/promises";
7
- import path, { basename, dirname, extname, join, posix, relative, resolve } from "node:path";
8
- import { definePrinter, isOperationNode, isSchemaNode } from "@kubb/ast";
9
- import { createFabric } from "@kubb/react-fabric";
10
- import { typescriptParser } from "@kubb/react-fabric/parsers";
11
- import { fsPlugin } from "@kubb/react-fabric/plugins";
12
- import { performance } from "node:perf_hooks";
13
- import { deflateSync } from "fflate";
14
- import { x } from "tinyexec";
5
+ import { dirname, join, resolve } from "node:path";
6
+ import * as ast from "@kubb/ast";
7
+ import { extractStringsFromNodes, transform, walk } from "@kubb/ast";
15
8
  import { version } from "node:process";
16
- import os from "node:os";
17
- import { pathToFileURL } from "node:url";
18
- import * as pkg from "empathic/package";
19
- import { coerce, satisfies } from "semver";
20
- import { sortBy } from "remeda";
21
- //#region ../../internals/utils/dist/index.js
22
- /** Thrown when a plugin's configuration or input fails validation. */
23
- var ValidationPluginError = class extends Error {};
9
+ //#region ../../internals/utils/src/errors.ts
24
10
  /**
25
11
  * Thrown when one or more errors occur during a Kubb build.
26
12
  * Carries the full list of underlying errors on `errors`.
13
+ *
14
+ * @example
15
+ * ```ts
16
+ * throw new BuildError('Build failed', { errors: [err1, err2] })
17
+ * ```
27
18
  */
28
19
  var BuildError = class extends Error {
29
20
  errors;
@@ -35,19 +26,34 @@ var BuildError = class extends Error {
35
26
  };
36
27
  /**
37
28
  * Coerces an unknown thrown value to an `Error` instance.
38
- * When the value is already an `Error` it is returned as-is;
39
- * otherwise a new `Error` is created whose message is `String(value)`.
29
+ * Returns the value as-is when it is already an `Error`; otherwise wraps it with `String(value)`.
30
+ *
31
+ * @example
32
+ * ```ts
33
+ * try { ... } catch(err) {
34
+ * throw new BuildError('Build failed', { cause: toError(err), errors: [] })
35
+ * }
36
+ * ```
40
37
  */
41
38
  function toError(value) {
42
39
  return value instanceof Error ? value : new Error(String(value));
43
40
  }
41
+ //#endregion
42
+ //#region ../../internals/utils/src/asyncEventEmitter.ts
44
43
  /**
45
- * A typed EventEmitter that awaits all async listeners before resolving.
44
+ * Typed `EventEmitter` that awaits all async listeners before resolving.
46
45
  * Wraps Node's `EventEmitter` with full TypeScript event-map inference.
46
+ *
47
+ * @example
48
+ * ```ts
49
+ * const emitter = new AsyncEventEmitter<{ build: [name: string] }>()
50
+ * emitter.on('build', async (name) => { console.log(name) })
51
+ * await emitter.emit('build', 'petstore') // all listeners awaited
52
+ * ```
47
53
  */
48
54
  var AsyncEventEmitter = class {
49
55
  /**
50
- * `maxListener` controls the maximum number of listeners per event before Node emits a memory-leak warning.
56
+ * Maximum number of listeners per event before Node emits a memory-leak warning.
51
57
  * @default 10
52
58
  */
53
59
  constructor(maxListener = 10) {
@@ -55,31 +61,48 @@ var AsyncEventEmitter = class {
55
61
  }
56
62
  #emitter = new EventEmitter();
57
63
  /**
58
- * Emits an event and awaits all registered listeners in parallel.
64
+ * Emits `eventName` and awaits all registered listeners sequentially.
59
65
  * Throws if any listener rejects, wrapping the cause with the event name and serialized arguments.
66
+ *
67
+ * @example
68
+ * ```ts
69
+ * await emitter.emit('build', 'petstore')
70
+ * ```
60
71
  */
61
72
  async emit(eventName, ...eventArgs) {
62
73
  const listeners = this.#emitter.listeners(eventName);
63
74
  if (listeners.length === 0) return;
64
- await Promise.all(listeners.map(async (listener) => {
75
+ for (const listener of listeners) try {
76
+ await listener(...eventArgs);
77
+ } catch (err) {
78
+ let serializedArgs;
65
79
  try {
66
- return await listener(...eventArgs);
67
- } catch (err) {
68
- let serializedArgs;
69
- try {
70
- serializedArgs = JSON.stringify(eventArgs);
71
- } catch {
72
- serializedArgs = String(eventArgs);
73
- }
74
- throw new Error(`Error in async listener for "${eventName}" with eventArgs ${serializedArgs}`, { cause: toError(err) });
80
+ serializedArgs = JSON.stringify(eventArgs);
81
+ } catch {
82
+ serializedArgs = String(eventArgs);
75
83
  }
76
- }));
84
+ throw new Error(`Error in async listener for "${eventName}" with eventArgs ${serializedArgs}`, { cause: toError(err) });
85
+ }
77
86
  }
78
- /** Registers a persistent listener for the given event. */
87
+ /**
88
+ * Registers a persistent listener for `eventName`.
89
+ *
90
+ * @example
91
+ * ```ts
92
+ * emitter.on('build', async (name) => { console.log(name) })
93
+ * ```
94
+ */
79
95
  on(eventName, handler) {
80
96
  this.#emitter.on(eventName, handler);
81
97
  }
82
- /** Registers a one-shot listener that removes itself after the first invocation. */
98
+ /**
99
+ * Registers a one-shot listener that removes itself after the first invocation.
100
+ *
101
+ * @example
102
+ * ```ts
103
+ * emitter.onOnce('build', async (name) => { console.log(name) })
104
+ * ```
105
+ */
83
106
  onOnce(eventName, handler) {
84
107
  const wrapper = (...args) => {
85
108
  this.off(eventName, wrapper);
@@ -87,237 +110,53 @@ var AsyncEventEmitter = class {
87
110
  };
88
111
  this.on(eventName, wrapper);
89
112
  }
90
- /** Removes a previously registered listener. */
113
+ /**
114
+ * Removes a previously registered listener.
115
+ *
116
+ * @example
117
+ * ```ts
118
+ * emitter.off('build', handler)
119
+ * ```
120
+ */
91
121
  off(eventName, handler) {
92
122
  this.#emitter.off(eventName, handler);
93
123
  }
94
- /** Removes all listeners from every event channel. */
124
+ /**
125
+ * Returns the number of listeners registered for `eventName`.
126
+ *
127
+ * @example
128
+ * ```ts
129
+ * emitter.on('build', handler)
130
+ * emitter.listenerCount('build') // 1
131
+ * ```
132
+ */
133
+ listenerCount(eventName) {
134
+ return this.#emitter.listenerCount(eventName);
135
+ }
136
+ /**
137
+ * Removes all listeners from every event channel.
138
+ *
139
+ * @example
140
+ * ```ts
141
+ * emitter.removeAll()
142
+ * ```
143
+ */
95
144
  removeAll() {
96
145
  this.#emitter.removeAllListeners();
97
146
  }
98
147
  };
148
+ //#endregion
149
+ //#region ../../internals/utils/src/time.ts
99
150
  /**
100
- * Shared implementation for camelCase and PascalCase conversion.
101
- * Splits on common word boundaries (spaces, hyphens, underscores, dots, slashes, colons)
102
- * and capitalizes each word according to `pascal`.
103
- *
104
- * When `pascal` is `true` the first word is also capitalized (PascalCase), otherwise only subsequent words are.
105
- */
106
- function toCamelOrPascal(text, pascal) {
107
- return text.trim().replace(/([a-z\d])([A-Z])/g, "$1 $2").replace(/([A-Z]+)([A-Z][a-z])/g, "$1 $2").replace(/(\d)([a-z])/g, "$1 $2").split(/[\s\-_./\\:]+/).filter(Boolean).map((word, i) => {
108
- if (word.length > 1 && word === word.toUpperCase()) return word;
109
- if (i === 0 && !pascal) return word.charAt(0).toLowerCase() + word.slice(1);
110
- return word.charAt(0).toUpperCase() + word.slice(1);
111
- }).join("").replace(/[^a-zA-Z0-9]/g, "");
112
- }
113
- /**
114
- * Splits `text` on `.` and applies `transformPart` to each segment.
115
- * The last segment receives `isLast = true`, all earlier segments receive `false`.
116
- * Segments are joined with `/` to form a file path.
117
- */
118
- function applyToFileParts(text, transformPart) {
119
- const parts = text.split(".");
120
- return parts.map((part, i) => transformPart(part, i === parts.length - 1)).join("/");
121
- }
122
- /**
123
- * Converts `text` to camelCase.
124
- * When `isFile` is `true`, dot-separated segments are each cased independently and joined with `/`.
151
+ * Calculates elapsed time in milliseconds from a high-resolution `process.hrtime` start time.
152
+ * Rounds to 2 decimal places for sub-millisecond precision without noise.
125
153
  *
126
154
  * @example
127
- * camelCase('hello-world') // 'helloWorld'
128
- * camelCase('pet.petId', { isFile: true }) // 'pet/petId'
129
- */
130
- function camelCase(text, { isFile, prefix = "", suffix = "" } = {}) {
131
- if (isFile) return applyToFileParts(text, (part, isLast) => camelCase(part, isLast ? {
132
- prefix,
133
- suffix
134
- } : {}));
135
- return toCamelOrPascal(`${prefix} ${text} ${suffix}`, false);
136
- }
137
- /** Returns a `CLIAdapter` with type inference. Pass a different adapter to `createCLI` to swap the CLI engine. */
138
- function defineCLIAdapter(adapter) {
139
- return adapter;
140
- }
141
- /**
142
- * Serializes `CommandDefinition[]` to a plain, JSON-serializable structure.
143
- * Use to expose CLI capabilities to AI agents or MCP tools.
144
- */
145
- function getCommandSchema(defs) {
146
- return defs.map(serializeCommand);
147
- }
148
- function serializeCommand(def) {
149
- return {
150
- name: def.name,
151
- description: def.description,
152
- arguments: def.arguments,
153
- options: serializeOptions(def.options ?? {}),
154
- subCommands: def.subCommands ? def.subCommands.map(serializeCommand) : []
155
- };
156
- }
157
- function serializeOptions(options) {
158
- return Object.entries(options).map(([name, opt]) => {
159
- return {
160
- name,
161
- flags: `${opt.short ? `-${opt.short}, ` : ""}--${name}${opt.type === "string" ? ` <${opt.hint ?? name}>` : ""}`,
162
- type: opt.type,
163
- description: opt.description,
164
- ...opt.default !== void 0 ? { default: opt.default } : {},
165
- ...opt.hint ? { hint: opt.hint } : {},
166
- ...opt.enum ? { enum: opt.enum } : {},
167
- ...opt.required ? { required: opt.required } : {}
168
- };
169
- });
170
- }
171
- /** Prints formatted help output for a command using its `CommandDefinition`. */
172
- function renderHelp(def, parentName) {
173
- const schema = getCommandSchema([def])[0];
174
- const programName = parentName ? `${parentName} ${schema.name}` : schema.name;
175
- const argsPart = schema.arguments?.length ? ` ${schema.arguments.join(" ")}` : "";
176
- const subCmdPart = schema.subCommands.length ? " <command>" : "";
177
- console.log(`\n${styleText("bold", "Usage:")} ${programName}${argsPart}${subCmdPart} [options]\n`);
178
- if (schema.description) console.log(` ${schema.description}\n`);
179
- if (schema.subCommands.length) {
180
- console.log(styleText("bold", "Commands:"));
181
- for (const sub of schema.subCommands) console.log(` ${styleText("cyan", sub.name.padEnd(16))}${sub.description}`);
182
- console.log();
183
- }
184
- const options = [...schema.options, {
185
- name: "help",
186
- flags: "-h, --help",
187
- type: "boolean",
188
- description: "Show help"
189
- }];
190
- console.log(styleText("bold", "Options:"));
191
- for (const opt of options) {
192
- const flags = styleText("cyan", opt.flags.padEnd(30));
193
- const defaultPart = opt.default !== void 0 ? styleText("dim", ` (default: ${opt.default})`) : "";
194
- console.log(` ${flags}${opt.description}${defaultPart}`);
195
- }
196
- console.log();
197
- }
198
- function buildParseOptions(def) {
199
- const result = { help: {
200
- type: "boolean",
201
- short: "h"
202
- } };
203
- for (const [name, opt] of Object.entries(def.options ?? {})) result[name] = {
204
- type: opt.type,
205
- ...opt.short ? { short: opt.short } : {},
206
- ...opt.default !== void 0 ? { default: opt.default } : {}
207
- };
208
- return result;
209
- }
210
- async function runCommand(def, argv, parentName) {
211
- const parseOptions = buildParseOptions(def);
212
- let parsed;
213
- try {
214
- const result = parseArgs({
215
- args: argv,
216
- options: parseOptions,
217
- allowPositionals: true,
218
- strict: false
219
- });
220
- parsed = {
221
- values: result.values,
222
- positionals: result.positionals
223
- };
224
- } catch {
225
- renderHelp(def, parentName);
226
- process.exit(1);
227
- }
228
- if (parsed.values["help"]) {
229
- renderHelp(def, parentName);
230
- process.exit(0);
231
- }
232
- for (const [name, opt] of Object.entries(def.options ?? {})) if (opt.required && parsed.values[name] === void 0) {
233
- console.error(styleText("red", `Error: --${name} is required`));
234
- renderHelp(def, parentName);
235
- process.exit(1);
236
- }
237
- if (!def.run) {
238
- renderHelp(def, parentName);
239
- process.exit(0);
240
- }
241
- try {
242
- await def.run(parsed);
243
- } catch (err) {
244
- console.error(styleText("red", `Error: ${err instanceof Error ? err.message : String(err)}`));
245
- renderHelp(def, parentName);
246
- process.exit(1);
247
- }
248
- }
249
- function printRootHelp(programName, version, defs) {
250
- console.log(`\n${styleText("bold", "Usage:")} ${programName} <command> [options]\n`);
251
- console.log(` Kubb generation — v${version}\n`);
252
- console.log(styleText("bold", "Commands:"));
253
- for (const def of defs) console.log(` ${styleText("cyan", def.name.padEnd(16))}${def.description}`);
254
- console.log();
255
- console.log(styleText("bold", "Options:"));
256
- console.log(` ${styleText("cyan", "-v, --version".padEnd(30))}Show version number`);
257
- console.log(` ${styleText("cyan", "-h, --help".padEnd(30))}Show help`);
258
- console.log();
259
- console.log(`Run ${styleText("cyan", `${programName} <command> --help`)} for command-specific help.\n`);
260
- }
261
- defineCLIAdapter({
262
- renderHelp(def, parentName) {
263
- renderHelp(def, parentName);
264
- },
265
- async run(defs, argv, opts) {
266
- const { programName, defaultCommandName, version } = opts;
267
- const args = argv.length >= 2 && argv[0]?.includes("node") ? argv.slice(2) : argv;
268
- if (args[0] === "--version" || args[0] === "-v") {
269
- console.log(version);
270
- process.exit(0);
271
- }
272
- if (args[0] === "--help" || args[0] === "-h") {
273
- printRootHelp(programName, version, defs);
274
- process.exit(0);
275
- }
276
- if (args.length === 0) {
277
- const defaultDef = defs.find((d) => d.name === defaultCommandName);
278
- if (defaultDef?.run) await runCommand(defaultDef, [], programName);
279
- else printRootHelp(programName, version, defs);
280
- return;
281
- }
282
- const [first, ...rest] = args;
283
- const isKnownSubcommand = defs.some((d) => d.name === first);
284
- let def;
285
- let commandArgv;
286
- let parentName;
287
- if (isKnownSubcommand) {
288
- def = defs.find((d) => d.name === first);
289
- commandArgv = rest;
290
- parentName = programName;
291
- } else {
292
- def = defs.find((d) => d.name === defaultCommandName);
293
- commandArgv = args;
294
- parentName = programName;
295
- }
296
- if (!def) {
297
- console.error(`Unknown command: ${first}`);
298
- printRootHelp(programName, version, defs);
299
- process.exit(1);
300
- }
301
- if (def.subCommands?.length) {
302
- const [subName, ...subRest] = commandArgv;
303
- const subDef = def.subCommands.find((s) => s.name === subName);
304
- if (subName === "--help" || subName === "-h") {
305
- renderHelp(def, parentName);
306
- process.exit(0);
307
- }
308
- if (!subDef) {
309
- renderHelp(def, parentName);
310
- process.exit(subName ? 1 : 0);
311
- }
312
- await runCommand(subDef, subRest, `${parentName} ${def.name}`);
313
- return;
314
- }
315
- await runCommand(def, commandArgv, parentName);
316
- }
317
- });
318
- /**
319
- * Calculates elapsed time in milliseconds from a high-resolution start time.
320
- * Rounds to 2 decimal places to provide sub-millisecond precision without noise.
155
+ * ```ts
156
+ * const start = process.hrtime()
157
+ * doWork()
158
+ * getElapsedMs(start) // 42.35
159
+ * ```
321
160
  */
322
161
  function getElapsedMs(hrStart) {
323
162
  const [seconds, nanoseconds] = process.hrtime(hrStart);
@@ -325,96 +164,62 @@ function getElapsedMs(hrStart) {
325
164
  return Math.round(ms * 100) / 100;
326
165
  }
327
166
  /**
328
- * Converts a millisecond duration into a human-readable string.
329
- * Adjusts units (ms, s, m s) based on the magnitude of the duration.
167
+ * Converts a millisecond duration into a human-readable string (`ms`, `s`, or `m s`).
168
+ *
169
+ * @example
170
+ * ```ts
171
+ * formatMs(250) // '250ms'
172
+ * formatMs(1500) // '1.50s'
173
+ * formatMs(90000) // '1m 30.0s'
174
+ * ```
330
175
  */
331
176
  function formatMs(ms) {
332
177
  if (ms >= 6e4) return `${Math.floor(ms / 6e4)}m ${(ms % 6e4 / 1e3).toFixed(1)}s`;
333
178
  if (ms >= 1e3) return `${(ms / 1e3).toFixed(2)}s`;
334
179
  return `${Math.round(ms)}ms`;
335
180
  }
336
- /**
337
- * Parses a CSS hex color string (`#RGB`) into its RGB channels.
338
- * Falls back to `255` for any channel that cannot be parsed.
339
- */
340
- function parseHex(color) {
341
- const int = Number.parseInt(color.replace("#", ""), 16);
342
- return Number.isNaN(int) ? {
343
- r: 255,
344
- g: 255,
345
- b: 255
346
- } : {
347
- r: int >> 16 & 255,
348
- g: int >> 8 & 255,
349
- b: int & 255
350
- };
351
- }
352
- /**
353
- * Returns a function that wraps a string in a 24-bit ANSI true-color escape sequence
354
- * for the given hex color.
355
- */
356
- function hex(color) {
357
- const { r, g, b } = parseHex(color);
358
- return (text) => `\x1b[38;2;${r};${g};${b}m${text}\x1b[0m`;
359
- }
360
- hex("#F55A17"), hex("#F5A217"), hex("#F58517"), hex("#B45309"), hex("#FFFFFF"), hex("#adadc6"), hex("#FDA4AF");
361
- /**
362
- * Converts all backslashes to forward slashes.
363
- * Extended-length Windows paths (`\\?\...`) are left unchanged.
364
- */
365
- function toSlash(p) {
366
- if (p.startsWith("\\\\?\\")) return p;
367
- return p.replaceAll("\\", "/");
368
- }
369
- /**
370
- * Returns the relative path from `rootDir` to `filePath`, always using
371
- * forward slashes and prefixed with `./` when not already traversing upward.
372
- */
373
- function getRelativePath(rootDir, filePath) {
374
- if (!rootDir || !filePath) throw new Error(`Root and file should be filled in when retrieving the relativePath, ${rootDir || ""} ${filePath || ""}`);
375
- const relativePath = posix.relative(toSlash(rootDir), toSlash(filePath));
376
- return relativePath.startsWith("../") ? relativePath : `./${relativePath}`;
377
- }
181
+ //#endregion
182
+ //#region ../../internals/utils/src/fs.ts
378
183
  /**
379
184
  * Resolves to `true` when the file or directory at `path` exists.
380
185
  * Uses `Bun.file().exists()` when running under Bun, `fs.access` otherwise.
186
+ *
187
+ * @example
188
+ * ```ts
189
+ * if (await exists('./kubb.config.ts')) {
190
+ * const content = await read('./kubb.config.ts')
191
+ * }
192
+ * ```
381
193
  */
382
194
  async function exists(path) {
383
195
  if (typeof Bun !== "undefined") return Bun.file(path).exists();
384
196
  return access(path).then(() => true, () => false);
385
197
  }
386
198
  /**
387
- * Reads the file at `path` as a UTF-8 string.
388
- * Uses `Bun.file().text()` when running under Bun, `fs.readFile` otherwise.
389
- */
390
- async function read(path) {
391
- if (typeof Bun !== "undefined") return Bun.file(path).text();
392
- return readFile(path, { encoding: "utf8" });
393
- }
394
- /** Synchronous counterpart of `read`. */
395
- function readSync(path) {
396
- return readFileSync(path, { encoding: "utf8" });
397
- }
398
- /**
399
199
  * Writes `data` to `path`, trimming leading/trailing whitespace before saving.
400
- * Skips the write and returns `undefined` when the trimmed content is empty or
401
- * identical to what is already on disk.
200
+ * Skips the write when the trimmed content is empty or identical to what is already on disk.
402
201
  * Creates any missing parent directories automatically.
403
- * When `sanity` is `true`, re-reads the file after writing and throws if the
404
- * content does not match.
202
+ * When `sanity` is `true`, re-reads the file after writing and throws if the content does not match.
203
+ *
204
+ * @example
205
+ * ```ts
206
+ * await write('./src/Pet.ts', source) // writes and returns trimmed content
207
+ * await write('./src/Pet.ts', source) // null — file unchanged
208
+ * await write('./src/Pet.ts', ' ') // null — empty content skipped
209
+ * ```
405
210
  */
406
211
  async function write(path, data, options = {}) {
407
212
  const trimmed = data.trim();
408
- if (trimmed === "") return void 0;
213
+ if (trimmed === "") return null;
409
214
  const resolved = resolve(path);
410
215
  if (typeof Bun !== "undefined") {
411
216
  const file = Bun.file(resolved);
412
- if ((await file.exists() ? await file.text() : null) === trimmed) return void 0;
217
+ if ((await file.exists() ? await file.text() : null) === trimmed) return null;
413
218
  await Bun.write(resolved, trimmed);
414
219
  return trimmed;
415
220
  }
416
221
  try {
417
- if (await readFile(resolved, { encoding: "utf-8" }) === trimmed) return void 0;
222
+ if (await readFile(resolved, { encoding: "utf-8" }) === trimmed) return null;
418
223
  } catch {}
419
224
  await mkdir(dirname(resolved), { recursive: true });
420
225
  await writeFile(resolved, trimmed, { encoding: "utf-8" });
@@ -425,128 +230,31 @@ async function write(path, data, options = {}) {
425
230
  }
426
231
  return trimmed;
427
232
  }
428
- /** Recursively removes `path`. Silently succeeds when `path` does not exist. */
233
+ /**
234
+ * Recursively removes `path`. Silently succeeds when `path` does not exist.
235
+ *
236
+ * @example
237
+ * ```ts
238
+ * await clean('./dist')
239
+ * ```
240
+ */
429
241
  async function clean(path) {
430
242
  return rm(path, {
431
243
  recursive: true,
432
244
  force: true
433
245
  });
434
246
  }
435
- /**
436
- * Registers `originalName` in `data` without altering the returned name.
437
- * Use this when you need to track usage frequency but always emit the original identifier.
438
- */
439
- function setUniqueName(originalName, data) {
440
- let used = data[originalName] || 0;
441
- if (used) {
442
- data[originalName] = ++used;
443
- return originalName;
444
- }
445
- data[originalName] = 1;
446
- return originalName;
447
- }
448
- /** Type guard for a rejected `Promise.allSettled` result with a typed `reason`. */
449
- function isPromiseRejectedResult(result) {
450
- return result.status === "rejected";
451
- }
452
- /**
453
- * JavaScript and Java reserved words.
454
- * @link https://github.com/jonschlinkert/reserved/blob/master/index.js
455
- */
456
- const reservedWords = [
457
- "abstract",
458
- "arguments",
459
- "boolean",
460
- "break",
461
- "byte",
462
- "case",
463
- "catch",
464
- "char",
465
- "class",
466
- "const",
467
- "continue",
468
- "debugger",
469
- "default",
470
- "delete",
471
- "do",
472
- "double",
473
- "else",
474
- "enum",
475
- "eval",
476
- "export",
477
- "extends",
478
- "false",
479
- "final",
480
- "finally",
481
- "float",
482
- "for",
483
- "function",
484
- "goto",
485
- "if",
486
- "implements",
487
- "import",
488
- "in",
489
- "instanceof",
490
- "int",
491
- "interface",
492
- "let",
493
- "long",
494
- "native",
495
- "new",
496
- "null",
497
- "package",
498
- "private",
499
- "protected",
500
- "public",
501
- "return",
502
- "short",
503
- "static",
504
- "super",
505
- "switch",
506
- "synchronized",
507
- "this",
508
- "throw",
509
- "throws",
510
- "transient",
511
- "true",
512
- "try",
513
- "typeof",
514
- "var",
515
- "void",
516
- "volatile",
517
- "while",
518
- "with",
519
- "yield",
520
- "Array",
521
- "Date",
522
- "hasOwnProperty",
523
- "Infinity",
524
- "isFinite",
525
- "isNaN",
526
- "isPrototypeOf",
527
- "length",
528
- "Math",
529
- "name",
530
- "NaN",
531
- "Number",
532
- "Object",
533
- "prototype",
534
- "String",
535
- "toString",
536
- "undefined",
537
- "valueOf"
538
- ];
539
- /**
540
- * Prefixes a word with `_` when it is a reserved JavaScript/Java identifier
541
- * or starts with a digit.
542
- */
543
- function transformReservedWord(word) {
544
- const firstChar = word.charCodeAt(0);
545
- if (word && (reservedWords.includes(word) || firstChar >= 48 && firstChar <= 57)) return `_${word}`;
546
- return word;
547
- }
247
+ //#endregion
248
+ //#region ../../internals/utils/src/reserved.ts
548
249
  /**
549
250
  * Returns `true` when `name` is a syntactically valid JavaScript variable name.
251
+ *
252
+ * @example
253
+ * ```ts
254
+ * isValidVarName('status') // true
255
+ * isValidVarName('class') // false (reserved word)
256
+ * isValidVarName('42foo') // false (starts with digit)
257
+ * ```
550
258
  */
551
259
  function isValidVarName(name) {
552
260
  try {
@@ -556,6 +264,8 @@ function isValidVarName(name) {
556
264
  }
557
265
  return true;
558
266
  }
267
+ //#endregion
268
+ //#region ../../internals/utils/src/urlPath.ts
559
269
  /**
560
270
  * Parses and transforms an OpenAPI/Swagger path string into various URL formats.
561
271
  *
@@ -565,18 +275,33 @@ function isValidVarName(name) {
565
275
  * p.template // '`/pet/${petId}`'
566
276
  */
567
277
  var URLPath = class {
568
- /** The raw OpenAPI/Swagger path string, e.g. `/pet/{petId}`. */
278
+ /**
279
+ * The raw OpenAPI/Swagger path string, e.g. `/pet/{petId}`.
280
+ */
569
281
  path;
570
282
  #options;
571
283
  constructor(path, options = {}) {
572
284
  this.path = path;
573
285
  this.#options = options;
574
286
  }
575
- /** Converts the OpenAPI path to Express-style colon syntax, e.g. `/pet/{petId}` → `/pet/:petId`. */
287
+ /** Converts the OpenAPI path to Express-style colon syntax, e.g. `/pet/{petId}` → `/pet/:petId`.
288
+ *
289
+ * @example
290
+ * ```ts
291
+ * new URLPath('/pet/{petId}').URL // '/pet/:petId'
292
+ * ```
293
+ */
576
294
  get URL() {
577
295
  return this.toURLPath();
578
296
  }
579
- /** Returns `true` when `path` is a fully-qualified URL (e.g. starts with `https://`). */
297
+ /** Returns `true` when `path` is a fully-qualified URL (e.g. starts with `https://`).
298
+ *
299
+ * @example
300
+ * ```ts
301
+ * new URLPath('https://petstore.swagger.io/v2/pet').isURL // true
302
+ * new URLPath('/pet/{petId}').isURL // false
303
+ * ```
304
+ */
580
305
  get isURL() {
581
306
  try {
582
307
  return !!new URL(this.path).href;
@@ -594,11 +319,25 @@ var URLPath = class {
594
319
  get template() {
595
320
  return this.toTemplateString();
596
321
  }
597
- /** Returns the path and its extracted params as a structured `URLObject`, or as a stringified expression when `stringify` is set. */
322
+ /** Returns the path and its extracted params as a structured `URLObject`, or as a stringified expression when `stringify` is set.
323
+ *
324
+ * @example
325
+ * ```ts
326
+ * new URLPath('/pet/{petId}').object
327
+ * // { url: '/pet/:petId', params: { petId: 'petId' } }
328
+ * ```
329
+ */
598
330
  get object() {
599
331
  return this.toObject();
600
332
  }
601
- /** Returns a map of path parameter names, or `undefined` when the path has no parameters. */
333
+ /** Returns a map of path parameter names, or `undefined` when the path has no parameters.
334
+ *
335
+ * @example
336
+ * ```ts
337
+ * new URLPath('/pet/{petId}').params // { petId: 'petId' }
338
+ * new URLPath('/pet').params // undefined
339
+ * ```
340
+ */
602
341
  get params() {
603
342
  return this.getParams();
604
343
  }
@@ -606,7 +345,9 @@ var URLPath = class {
606
345
  const param = isValidVarName(raw) ? raw : camelCase(raw);
607
346
  return this.#options.casing === "camelcase" ? camelCase(param) : param;
608
347
  }
609
- /** Iterates over every `{param}` token in `path`, calling `fn` with the raw token and transformed name. */
348
+ /**
349
+ * Iterates over every `{param}` token in `path`, calling `fn` with the raw token and transformed name.
350
+ */
610
351
  #eachParam(fn) {
611
352
  for (const match of this.path.matchAll(/\{([^}]+)\}/g)) {
612
353
  const raw = match[1];
@@ -643,6 +384,12 @@ var URLPath = class {
643
384
  * Extracts all `{param}` segments from the path and returns them as a key-value map.
644
385
  * An optional `replacer` transforms each parameter name in both key and value positions.
645
386
  * Returns `undefined` when no path parameters are found.
387
+ *
388
+ * @example
389
+ * ```ts
390
+ * new URLPath('/pet/{petId}/tag/{tagId}').getParams()
391
+ * // { petId: 'petId', tagId: 'tagId' }
392
+ * ```
646
393
  */
647
394
  getParams(replacer) {
648
395
  const params = {};
@@ -652,130 +399,43 @@ var URLPath = class {
652
399
  });
653
400
  return Object.keys(params).length > 0 ? params : void 0;
654
401
  }
655
- /** Converts the OpenAPI path to Express-style colon syntax, e.g. `/pet/{petId}` → `/pet/:petId`. */
402
+ /** Converts the OpenAPI path to Express-style colon syntax.
403
+ *
404
+ * @example
405
+ * ```ts
406
+ * new URLPath('/pet/{petId}').toURLPath() // '/pet/:petId'
407
+ * ```
408
+ */
656
409
  toURLPath() {
657
410
  return this.path.replace(/\{([^}]+)\}/g, ":$1");
658
411
  }
659
412
  };
660
413
  //#endregion
661
- //#region src/config.ts
662
- function defineConfig(config) {
663
- return config;
664
- }
665
- /**
666
- * Type guard to check if a given config has an `input.path`.
667
- */
668
- function isInputPath(config) {
669
- return typeof config?.input === "object" && config.input !== null && "path" in config.input;
670
- }
671
- //#endregion
672
- //#region src/constants.ts
673
- const DEFAULT_STUDIO_URL = "https://studio.kubb.dev";
674
- const BARREL_FILENAME = "index.ts";
675
- const DEFAULT_BANNER = "simple";
676
- const DEFAULT_EXTENSION = { ".ts": ".ts" };
677
- const PATH_SEPARATORS = ["/", "\\"];
678
- const logLevel = {
679
- silent: Number.NEGATIVE_INFINITY,
680
- error: 0,
681
- warn: 1,
682
- info: 3,
683
- verbose: 4,
684
- debug: 5
685
- };
686
- const linters = {
687
- eslint: {
688
- command: "eslint",
689
- args: (outputPath) => [outputPath, "--fix"],
690
- errorMessage: "Eslint not found"
691
- },
692
- biome: {
693
- command: "biome",
694
- args: (outputPath) => [
695
- "lint",
696
- "--fix",
697
- outputPath
698
- ],
699
- errorMessage: "Biome not found"
700
- },
701
- oxlint: {
702
- command: "oxlint",
703
- args: (outputPath) => ["--fix", outputPath],
704
- errorMessage: "Oxlint not found"
705
- }
706
- };
707
- const formatters = {
708
- prettier: {
709
- command: "prettier",
710
- args: (outputPath) => [
711
- "--ignore-unknown",
712
- "--write",
713
- outputPath
714
- ],
715
- errorMessage: "Prettier not found"
716
- },
717
- biome: {
718
- command: "biome",
719
- args: (outputPath) => [
720
- "format",
721
- "--write",
722
- outputPath
723
- ],
724
- errorMessage: "Biome not found"
725
- },
726
- oxfmt: {
727
- command: "oxfmt",
728
- args: (outputPath) => [outputPath],
729
- errorMessage: "Oxfmt not found"
730
- }
731
- };
732
- //#endregion
733
- //#region src/devtools.ts
414
+ //#region src/createAdapter.ts
734
415
  /**
735
- * Encodes a `RootNode` as a compressed, URL-safe string.
416
+ * Creates an adapter factory. Call the returned function with optional options to get the adapter instance.
736
417
  *
737
- * The JSON representation is deflate-compressed with {@link deflateSync} before
738
- * base64url encoding, which typically reduces payload size by 70–80 % and
739
- * keeps URLs well within browser and server path-length limits.
740
- *
741
- * Use {@link decodeAst} to reverse.
742
- */
743
- function encodeAst(root) {
744
- const compressed = deflateSync(new TextEncoder().encode(JSON.stringify(root)));
745
- return Buffer.from(compressed).toString("base64url");
746
- }
747
- /**
748
- * Constructs the Kubb Studio URL for the given `RootNode`.
749
- * When `options.ast` is `true`, navigates to the AST inspector (`/ast`).
750
- * The `root` is encoded and attached as the `?root=` query parameter so Studio
751
- * can decode and render it without a round-trip to any server.
752
- */
753
- function getStudioUrl(root, studioUrl, options = {}) {
754
- return `${studioUrl.replace(/\/$/, "")}${options.ast ? "/ast" : ""}?root=${encodeAst(root)}`;
755
- }
756
- /**
757
- * Opens the Kubb Studio URL for the given `RootNode` in the default browser —
418
+ * @example
419
+ * export const myAdapter = createAdapter<MyAdapter>((options) => {
420
+ * return {
421
+ * name: 'my-adapter',
422
+ * options,
423
+ * async parse(source) { ... },
424
+ * }
425
+ * })
758
426
  *
759
- * Falls back to printing the URL if the browser cannot be launched.
427
+ * // instantiate
428
+ * const adapter = myAdapter({ validate: true })
760
429
  */
761
- async function openInStudio(root, studioUrl, options = {}) {
762
- const url = getStudioUrl(root, studioUrl, options);
763
- const cmd = process.platform === "win32" ? "cmd" : process.platform === "darwin" ? "open" : "xdg-open";
764
- const args = process.platform === "win32" ? [
765
- "/c",
766
- "start",
767
- "",
768
- url
769
- ] : [url];
770
- try {
771
- await x(cmd, args);
772
- } catch {
773
- console.log(`\n ${url}\n`);
774
- }
430
+ function createAdapter(build) {
431
+ return (options) => build(options ?? {});
775
432
  }
776
433
  //#endregion
777
434
  //#region ../../node_modules/.pnpm/yocto-queue@1.2.2/node_modules/yocto-queue/index.js
778
- var Node = class {
435
+ var Node$1 = class {
436
+ static {
437
+ __name(this, "Node");
438
+ }
779
439
  value;
780
440
  next;
781
441
  constructor(value) {
@@ -790,7 +450,7 @@ var Queue = class {
790
450
  this.clear();
791
451
  }
792
452
  enqueue(value) {
793
- const node = new Node(value);
453
+ const node = new Node$1(value);
794
454
  if (this.#head) {
795
455
  this.#tail.next = node;
796
456
  this.#tail = node;
@@ -901,477 +561,87 @@ function validateConcurrency(concurrency) {
901
561
  if (!((Number.isInteger(concurrency) || concurrency === Number.POSITIVE_INFINITY) && concurrency > 0)) throw new TypeError("Expected `concurrency` to be a number from 1 and up");
902
562
  }
903
563
  //#endregion
904
- //#region src/utils/executeStrategies.ts
905
- /**
906
- * Chains promises
907
- */
908
- function hookSeq(promises) {
909
- return promises.filter(Boolean).reduce((promise, func) => {
910
- if (typeof func !== "function") throw new Error("HookSeq needs a function that returns a promise `() => Promise<unknown>`");
911
- return promise.then((state) => {
912
- const calledFunc = func(state);
913
- if (calledFunc) return calledFunc.then(Array.prototype.concat.bind(state));
914
- return state;
915
- });
916
- }, Promise.resolve([]));
564
+ //#region src/FileProcessor.ts
565
+ function joinSources(file) {
566
+ return file.sources.map((item) => extractStringsFromNodes(item.nodes)).filter(Boolean).join("\n\n");
917
567
  }
918
568
  /**
919
- * Chains promises, first non-null result stops and returns
569
+ * Converts a single file to a string using the registered parsers.
570
+ * Falls back to joining source values when no matching parser is found.
571
+ *
572
+ * @internal
920
573
  */
921
- function hookFirst(promises, nullCheck = (state) => state !== null) {
922
- let promise = Promise.resolve(null);
923
- for (const func of promises.filter(Boolean)) promise = promise.then((state) => {
924
- if (nullCheck(state)) return state;
925
- return func(state);
926
- });
927
- return promise;
928
- }
574
+ var FileProcessor = class {
575
+ #limit = pLimit(100);
576
+ async parse(file, { parsers, extension } = {}) {
577
+ const parseExtName = extension?.[file.extname] || void 0;
578
+ if (!parsers || !file.extname) return joinSources(file);
579
+ const parser = parsers.get(file.extname);
580
+ if (!parser) return joinSources(file);
581
+ return parser.parse(file, { extname: parseExtName });
582
+ }
583
+ async run(files, { parsers, mode = "sequential", extension, onStart, onEnd, onUpdate } = {}) {
584
+ await onStart?.(files);
585
+ const total = files.length;
586
+ let processed = 0;
587
+ const processOne = async (file) => {
588
+ const source = await this.parse(file, {
589
+ extension,
590
+ parsers
591
+ });
592
+ const currentProcessed = ++processed;
593
+ const percentage = currentProcessed / total * 100;
594
+ await onUpdate?.({
595
+ file,
596
+ source,
597
+ processed: currentProcessed,
598
+ percentage,
599
+ total
600
+ });
601
+ };
602
+ if (mode === "sequential") for (const file of files) await processOne(file);
603
+ else await Promise.all(files.map((file) => this.#limit(() => processOne(file))));
604
+ await onEnd?.(files);
605
+ return files;
606
+ }
607
+ };
608
+ //#endregion
609
+ //#region src/createStorage.ts
929
610
  /**
930
- * Runs an array of promise functions with optional concurrency limit.
611
+ * Creates a storage factory. Call the returned function with optional options to get the storage instance.
612
+ *
613
+ * @example
614
+ * export const memoryStorage = createStorage(() => {
615
+ * const store = new Map<string, string>()
616
+ * return {
617
+ * name: 'memory',
618
+ * async hasItem(key) { return store.has(key) },
619
+ * async getItem(key) { return store.get(key) ?? null },
620
+ * async setItem(key, value) { store.set(key, value) },
621
+ * async removeItem(key) { store.delete(key) },
622
+ * async getKeys(base) {
623
+ * const keys = [...store.keys()]
624
+ * return base ? keys.filter((k) => k.startsWith(base)) : keys
625
+ * },
626
+ * async clear(base) { if (!base) store.clear() },
627
+ * }
628
+ * })
931
629
  */
932
- function hookParallel(promises, concurrency = Number.POSITIVE_INFINITY) {
933
- const limit = pLimit(concurrency);
934
- const tasks = promises.filter(Boolean).map((promise) => limit(() => promise()));
935
- return Promise.allSettled(tasks);
630
+ function createStorage(build) {
631
+ return (options) => build(options ?? {});
936
632
  }
937
633
  //#endregion
938
- //#region src/PluginDriver.ts
939
- function getMode(fileOrFolder) {
940
- if (!fileOrFolder) return "split";
941
- return extname(fileOrFolder) ? "single" : "split";
634
+ //#region src/storages/fsStorage.ts
635
+ /**
636
+ * Detects the filesystem error used to indicate that a path does not exist.
637
+ */
638
+ function isMissingPathError(error) {
639
+ return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
942
640
  }
943
- const hookFirstNullCheck = (state) => !!state?.result;
944
- var PluginDriver = class {
945
- config;
946
- options;
947
- /**
948
- * The universal `@kubb/ast` `RootNode` produced by the adapter, set by
949
- * the build pipeline after the adapter's `parse()` resolves.
950
- */
951
- rootNode = void 0;
952
- adapter = void 0;
953
- #studioIsOpen = false;
954
- #plugins = /* @__PURE__ */ new Set();
955
- #usedPluginNames = {};
956
- constructor(config, options) {
957
- this.config = config;
958
- this.options = options;
959
- [...config.plugins || []].forEach((plugin) => {
960
- const parsedPlugin = this.#parse(plugin);
961
- this.#plugins.add(parsedPlugin);
962
- });
963
- }
964
- get events() {
965
- return this.options.events;
966
- }
967
- getContext(plugin) {
968
- const plugins = [...this.#plugins];
969
- const driver = this;
970
- const baseContext = {
971
- fabric: this.options.fabric,
972
- config: this.config,
973
- plugin,
974
- events: this.options.events,
975
- driver: this,
976
- mode: getMode(resolve(this.config.root, this.config.output.path)),
977
- addFile: async (...files) => {
978
- await this.options.fabric.addFile(...files);
979
- },
980
- upsertFile: async (...files) => {
981
- await this.options.fabric.upsertFile(...files);
982
- },
983
- get rootNode() {
984
- return driver.rootNode;
985
- },
986
- get adapter() {
987
- return driver.adapter;
988
- },
989
- openInStudio(options) {
990
- if (!driver.config.devtools || driver.#studioIsOpen) return;
991
- if (typeof driver.config.devtools !== "object") throw new Error("Devtools must be an object");
992
- if (!driver.rootNode || !driver.adapter) throw new Error("adapter is not defined, make sure you have set the parser in kubb.config.ts");
993
- driver.#studioIsOpen = true;
994
- const studioUrl = driver.config.devtools?.studioUrl ?? "https://studio.kubb.dev";
995
- return openInStudio(driver.rootNode, studioUrl, options);
996
- }
997
- };
998
- const mergedExtras = {};
999
- for (const p of plugins) if (typeof p.inject === "function") {
1000
- const result = p.inject.call(baseContext, baseContext);
1001
- if (result !== null && typeof result === "object") Object.assign(mergedExtras, result);
1002
- }
1003
- return {
1004
- ...baseContext,
1005
- ...mergedExtras
1006
- };
1007
- }
1008
- get plugins() {
1009
- return this.#getSortedPlugins();
1010
- }
1011
- getFile({ name, mode, extname, pluginName, options }) {
1012
- const resolvedName = mode ? mode === "single" ? "" : this.resolveName({
1013
- name,
1014
- pluginName,
1015
- type: "file"
1016
- }) : name;
1017
- const path = this.resolvePath({
1018
- baseName: `${resolvedName}${extname}`,
1019
- mode,
1020
- pluginName,
1021
- options
1022
- });
1023
- if (!path) throw new Error(`Filepath should be defined for resolvedName "${resolvedName}" and pluginName "${pluginName}"`);
1024
- return {
1025
- path,
1026
- baseName: basename(path),
1027
- meta: { pluginName },
1028
- sources: [],
1029
- imports: [],
1030
- exports: []
1031
- };
1032
- }
1033
- resolvePath = (params) => {
1034
- const defaultPath = resolve(resolve(this.config.root, this.config.output.path), params.baseName);
1035
- if (params.pluginName) return this.hookForPluginSync({
1036
- pluginName: params.pluginName,
1037
- hookName: "resolvePath",
1038
- parameters: [
1039
- params.baseName,
1040
- params.mode,
1041
- params.options
1042
- ]
1043
- })?.at(0) || defaultPath;
1044
- return this.hookFirstSync({
1045
- hookName: "resolvePath",
1046
- parameters: [
1047
- params.baseName,
1048
- params.mode,
1049
- params.options
1050
- ]
1051
- })?.result || defaultPath;
1052
- };
1053
- resolveName = (params) => {
1054
- if (params.pluginName) {
1055
- const names = this.hookForPluginSync({
1056
- pluginName: params.pluginName,
1057
- hookName: "resolveName",
1058
- parameters: [params.name.trim(), params.type]
1059
- });
1060
- return transformReservedWord([...new Set(names)].at(0) || params.name);
1061
- }
1062
- const name = this.hookFirstSync({
1063
- hookName: "resolveName",
1064
- parameters: [params.name.trim(), params.type]
1065
- })?.result;
1066
- return transformReservedWord(name ?? params.name);
1067
- };
1068
- /**
1069
- * Run a specific hookName for plugin x.
1070
- */
1071
- async hookForPlugin({ pluginName, hookName, parameters }) {
1072
- const plugins = this.getPluginsByName(hookName, pluginName);
1073
- this.events.emit("plugins:hook:progress:start", {
1074
- hookName,
1075
- plugins
1076
- });
1077
- const items = [];
1078
- for (const plugin of plugins) {
1079
- const result = await this.#execute({
1080
- strategy: "hookFirst",
1081
- hookName,
1082
- parameters,
1083
- plugin
1084
- });
1085
- if (result !== void 0 && result !== null) items.push(result);
1086
- }
1087
- this.events.emit("plugins:hook:progress:end", { hookName });
1088
- return items;
1089
- }
1090
- /**
1091
- * Run a specific hookName for plugin x.
1092
- */
1093
- hookForPluginSync({ pluginName, hookName, parameters }) {
1094
- return this.getPluginsByName(hookName, pluginName).map((plugin) => {
1095
- return this.#executeSync({
1096
- strategy: "hookFirst",
1097
- hookName,
1098
- parameters,
1099
- plugin
1100
- });
1101
- }).filter((x) => x !== null);
1102
- }
1103
- /**
1104
- * Returns the first non-null result.
1105
- */
1106
- async hookFirst({ hookName, parameters, skipped }) {
1107
- const plugins = this.#getSortedPlugins(hookName).filter((plugin) => {
1108
- return skipped ? !skipped.has(plugin) : true;
1109
- });
1110
- this.events.emit("plugins:hook:progress:start", {
1111
- hookName,
1112
- plugins
1113
- });
1114
- const result = await hookFirst(plugins.map((plugin) => {
1115
- return async () => {
1116
- const value = await this.#execute({
1117
- strategy: "hookFirst",
1118
- hookName,
1119
- parameters,
1120
- plugin
1121
- });
1122
- return Promise.resolve({
1123
- plugin,
1124
- result: value
1125
- });
1126
- };
1127
- }), hookFirstNullCheck);
1128
- this.events.emit("plugins:hook:progress:end", { hookName });
1129
- return result;
1130
- }
1131
- /**
1132
- * Returns the first non-null result.
1133
- */
1134
- hookFirstSync({ hookName, parameters, skipped }) {
1135
- let parseResult = null;
1136
- const plugins = this.#getSortedPlugins(hookName).filter((plugin) => {
1137
- return skipped ? !skipped.has(plugin) : true;
1138
- });
1139
- for (const plugin of plugins) {
1140
- parseResult = {
1141
- result: this.#executeSync({
1142
- strategy: "hookFirst",
1143
- hookName,
1144
- parameters,
1145
- plugin
1146
- }),
1147
- plugin
1148
- };
1149
- if (parseResult?.result != null) break;
1150
- }
1151
- return parseResult;
1152
- }
1153
- /**
1154
- * Runs all plugins in parallel based on `this.plugin` order and `pre`/`post` settings.
1155
- */
1156
- async hookParallel({ hookName, parameters }) {
1157
- const plugins = this.#getSortedPlugins(hookName);
1158
- this.events.emit("plugins:hook:progress:start", {
1159
- hookName,
1160
- plugins
1161
- });
1162
- const pluginStartTimes = /* @__PURE__ */ new Map();
1163
- const results = await hookParallel(plugins.map((plugin) => {
1164
- return () => {
1165
- pluginStartTimes.set(plugin, performance.now());
1166
- return this.#execute({
1167
- strategy: "hookParallel",
1168
- hookName,
1169
- parameters,
1170
- plugin
1171
- });
1172
- };
1173
- }), this.options.concurrency);
1174
- results.forEach((result, index) => {
1175
- if (isPromiseRejectedResult(result)) {
1176
- const plugin = this.#getSortedPlugins(hookName)[index];
1177
- if (plugin) {
1178
- const startTime = pluginStartTimes.get(plugin) ?? performance.now();
1179
- this.events.emit("error", result.reason, {
1180
- plugin,
1181
- hookName,
1182
- strategy: "hookParallel",
1183
- duration: Math.round(performance.now() - startTime),
1184
- parameters
1185
- });
1186
- }
1187
- }
1188
- });
1189
- this.events.emit("plugins:hook:progress:end", { hookName });
1190
- return results.reduce((acc, result) => {
1191
- if (result.status === "fulfilled") acc.push(result.value);
1192
- return acc;
1193
- }, []);
1194
- }
1195
- /**
1196
- * Chains plugins
1197
- */
1198
- async hookSeq({ hookName, parameters }) {
1199
- const plugins = this.#getSortedPlugins(hookName);
1200
- this.events.emit("plugins:hook:progress:start", {
1201
- hookName,
1202
- plugins
1203
- });
1204
- await hookSeq(plugins.map((plugin) => {
1205
- return () => this.#execute({
1206
- strategy: "hookSeq",
1207
- hookName,
1208
- parameters,
1209
- plugin
1210
- });
1211
- }));
1212
- this.events.emit("plugins:hook:progress:end", { hookName });
1213
- }
1214
- #getSortedPlugins(hookName) {
1215
- const plugins = [...this.#plugins];
1216
- if (hookName) return plugins.filter((plugin) => hookName in plugin);
1217
- return plugins.map((plugin) => {
1218
- if (plugin.pre) {
1219
- let missingPlugins = plugin.pre.filter((pluginName) => !plugins.find((pluginToFind) => pluginToFind.name === pluginName));
1220
- if (missingPlugins.includes("plugin-oas") && this.adapter) missingPlugins = missingPlugins.filter((pluginName) => pluginName !== "plugin-oas");
1221
- if (missingPlugins.length > 0) throw new ValidationPluginError(`The plugin '${plugin.name}' has a pre set that references missing plugins for '${missingPlugins.join(", ")}'`);
1222
- }
1223
- return plugin;
1224
- }).sort((a, b) => {
1225
- if (b.pre?.includes(a.name)) return 1;
1226
- if (b.post?.includes(a.name)) return -1;
1227
- return 0;
1228
- });
1229
- }
1230
- getPluginByName(pluginName) {
1231
- return [...this.#plugins].find((item) => item.name === pluginName);
1232
- }
1233
- getPluginsByName(hookName, pluginName) {
1234
- const plugins = [...this.plugins];
1235
- const pluginByPluginName = plugins.filter((plugin) => hookName in plugin).filter((item) => item.name === pluginName);
1236
- if (!pluginByPluginName?.length) {
1237
- const corePlugin = plugins.find((plugin) => plugin.name === "core" && hookName in plugin);
1238
- return corePlugin ? [corePlugin] : [];
1239
- }
1240
- return pluginByPluginName;
1241
- }
1242
- /**
1243
- * Run an async plugin hook and return the result.
1244
- * @param hookName Name of the plugin hook. Must be either in `PluginHooks` or `OutputPluginValueHooks`.
1245
- * @param args Arguments passed to the plugin hook.
1246
- * @param plugin The actual pluginObject to run.
1247
- */
1248
- #emitProcessingEnd({ startTime, output, strategy, hookName, plugin, parameters }) {
1249
- this.events.emit("plugins:hook:processing:end", {
1250
- duration: Math.round(performance.now() - startTime),
1251
- parameters,
1252
- output,
1253
- strategy,
1254
- hookName,
1255
- plugin
1256
- });
1257
- }
1258
- #execute({ strategy, hookName, parameters, plugin }) {
1259
- const hook = plugin[hookName];
1260
- if (!hook) return null;
1261
- this.events.emit("plugins:hook:processing:start", {
1262
- strategy,
1263
- hookName,
1264
- parameters,
1265
- plugin
1266
- });
1267
- const startTime = performance.now();
1268
- return (async () => {
1269
- try {
1270
- const output = typeof hook === "function" ? await Promise.resolve(hook.apply(this.getContext(plugin), parameters ?? [])) : hook;
1271
- this.#emitProcessingEnd({
1272
- startTime,
1273
- output,
1274
- strategy,
1275
- hookName,
1276
- plugin,
1277
- parameters
1278
- });
1279
- return output;
1280
- } catch (error) {
1281
- this.events.emit("error", error, {
1282
- plugin,
1283
- hookName,
1284
- strategy,
1285
- duration: Math.round(performance.now() - startTime)
1286
- });
1287
- return null;
1288
- }
1289
- })();
1290
- }
1291
- /**
1292
- * Run a sync plugin hook and return the result.
1293
- * @param hookName Name of the plugin hook. Must be in `PluginHooks`.
1294
- * @param args Arguments passed to the plugin hook.
1295
- * @param plugin The actual plugin
1296
- */
1297
- #executeSync({ strategy, hookName, parameters, plugin }) {
1298
- const hook = plugin[hookName];
1299
- if (!hook) return null;
1300
- this.events.emit("plugins:hook:processing:start", {
1301
- strategy,
1302
- hookName,
1303
- parameters,
1304
- plugin
1305
- });
1306
- const startTime = performance.now();
1307
- try {
1308
- const output = typeof hook === "function" ? hook.apply(this.getContext(plugin), parameters) : hook;
1309
- this.#emitProcessingEnd({
1310
- startTime,
1311
- output,
1312
- strategy,
1313
- hookName,
1314
- plugin,
1315
- parameters
1316
- });
1317
- return output;
1318
- } catch (error) {
1319
- this.events.emit("error", error, {
1320
- plugin,
1321
- hookName,
1322
- strategy,
1323
- duration: Math.round(performance.now() - startTime)
1324
- });
1325
- return null;
1326
- }
1327
- }
1328
- #parse(plugin) {
1329
- const usedPluginNames = this.#usedPluginNames;
1330
- setUniqueName(plugin.name, usedPluginNames);
1331
- const usageCount = usedPluginNames[plugin.name];
1332
- 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.`);
1333
- return {
1334
- install() {},
1335
- ...plugin
1336
- };
1337
- }
1338
- };
1339
- //#endregion
1340
- //#region src/defineStorage.ts
1341
- /**
1342
- * Wraps a storage builder so the `options` argument is optional, following the
1343
- * same factory pattern as `definePlugin`, `defineLogger`, and `defineAdapter`.
1344
- *
1345
- * The builder receives the resolved options object and must return a
1346
- * `DefineStorage`-compatible object that includes a `name` string.
1347
- *
1348
- * @example
1349
- * ```ts
1350
- * import { defineStorage } from '@kubb/core'
1351
- *
1352
- * export const memoryStorage = defineStorage((_options) => {
1353
- * const store = new Map<string, string>()
1354
- * return {
1355
- * name: 'memory',
1356
- * async hasItem(key) { return store.has(key) },
1357
- * async getItem(key) { return store.get(key) ?? null },
1358
- * async setItem(key, value) { store.set(key, value) },
1359
- * async removeItem(key) { store.delete(key) },
1360
- * async getKeys() { return [...store.keys()] },
1361
- * async clear() { store.clear() },
1362
- * }
1363
- * })
1364
- * ```
1365
- */
1366
- function defineStorage(build) {
1367
- return (options) => build(options ?? {});
1368
- }
1369
- //#endregion
1370
- //#region src/storages/fsStorage.ts
1371
641
  /**
1372
642
  * Built-in filesystem storage driver.
1373
643
  *
1374
- * This is the default storage when no `storage` option is configured in `output`.
644
+ * This is the default storage when no `storage` option is configured in the root config.
1375
645
  * Keys are resolved against `process.cwd()`, so root-relative paths such as
1376
646
  * `src/gen/api/getPets.ts` are written to the correct location without extra configuration.
1377
647
  *
@@ -1383,29 +653,33 @@ function defineStorage(build) {
1383
653
  *
1384
654
  * @example
1385
655
  * ```ts
1386
- * import { defineConfig, fsStorage } from '@kubb/core'
656
+ * import { fsStorage } from '@kubb/core'
657
+ * import { defineConfig } from 'kubb'
1387
658
  *
1388
659
  * export default defineConfig({
1389
660
  * input: { path: './petStore.yaml' },
1390
- * output: { path: './src/gen', storage: fsStorage() },
661
+ * output: { path: './src/gen' },
662
+ * storage: fsStorage(),
1391
663
  * })
1392
664
  * ```
1393
665
  */
1394
- const fsStorage = defineStorage(() => ({
666
+ const fsStorage = createStorage(() => ({
1395
667
  name: "fs",
1396
668
  async hasItem(key) {
1397
669
  try {
1398
670
  await access(resolve(key));
1399
671
  return true;
1400
- } catch {
1401
- return false;
672
+ } catch (error) {
673
+ if (isMissingPathError(error)) return false;
674
+ throw new Error(`Failed to access storage item "${key}"`, { cause: error });
1402
675
  }
1403
676
  },
1404
677
  async getItem(key) {
1405
678
  try {
1406
679
  return await readFile(resolve(key), "utf8");
1407
- } catch {
1408
- return null;
680
+ } catch (error) {
681
+ if (isMissingPathError(error)) return null;
682
+ throw new Error(`Failed to read storage item "${key}"`, { cause: error });
1409
683
  }
1410
684
  },
1411
685
  async setItem(key, value) {
@@ -1416,12 +690,14 @@ const fsStorage = defineStorage(() => ({
1416
690
  },
1417
691
  async getKeys(base) {
1418
692
  const keys = [];
693
+ const resolvedBase = resolve(base ?? process.cwd());
1419
694
  async function walk(dir, prefix) {
1420
695
  let entries;
1421
696
  try {
1422
697
  entries = await readdir(dir, { withFileTypes: true });
1423
- } catch {
1424
- return;
698
+ } catch (error) {
699
+ if (isMissingPathError(error)) return;
700
+ throw new Error(`Failed to list storage keys under "${resolvedBase}"`, { cause: error });
1425
701
  }
1426
702
  for (const entry of entries) {
1427
703
  const rel = prefix ? `${prefix}/${entry.name}` : entry.name;
@@ -1429,7 +705,7 @@ const fsStorage = defineStorage(() => ({
1429
705
  else keys.push(rel);
1430
706
  }
1431
707
  }
1432
- await walk(resolve(base ?? process.cwd()), "");
708
+ await walk(resolvedBase, "");
1433
709
  return keys;
1434
710
  },
1435
711
  async clear(base) {
@@ -1439,11 +715,14 @@ const fsStorage = defineStorage(() => ({
1439
715
  }));
1440
716
  //#endregion
1441
717
  //#region package.json
1442
- var version$1 = "5.0.0-alpha.7";
718
+ var version$1 = "5.0.0-alpha.70";
1443
719
  //#endregion
1444
720
  //#region src/utils/diagnostics.ts
1445
721
  /**
1446
- * Get diagnostic information for debugging
722
+ * Returns a snapshot of the current runtime environment.
723
+ *
724
+ * Useful for attaching context to debug logs and error reports so that
725
+ * issues can be reproduced without manual information gathering.
1447
726
  */
1448
727
  function getDiagnosticInfo() {
1449
728
  return {
@@ -1455,13 +734,18 @@ function getDiagnosticInfo() {
1455
734
  };
1456
735
  }
1457
736
  //#endregion
1458
- //#region src/build.ts
1459
- async function setup(options) {
1460
- const { config: userConfig, events = new AsyncEventEmitter() } = options;
737
+ //#region src/utils/isInputPath.ts
738
+ function isInputPath(config) {
739
+ return typeof config?.input === "object" && config.input !== null && "path" in config.input;
740
+ }
741
+ //#endregion
742
+ //#region src/createKubb.ts
743
+ async function setup(userConfig, options = {}) {
744
+ const hooks = options.hooks ?? new AsyncEventEmitter();
1461
745
  const sources = /* @__PURE__ */ new Map();
1462
746
  const diagnosticInfo = getDiagnosticInfo();
1463
- if (Array.isArray(userConfig.input)) await events.emit("warn", "This feature is still under development — use with caution");
1464
- await events.emit("debug", {
747
+ if (Array.isArray(userConfig.input)) await hooks.emit("kubb:warn", { message: "This feature is still under development — use with caution" });
748
+ await hooks.emit("kubb:debug", {
1465
749
  date: /* @__PURE__ */ new Date(),
1466
750
  logs: [
1467
751
  "Configuration:",
@@ -1470,7 +754,7 @@ async function setup(options) {
1470
754
  ` • Output: ${userConfig.output?.path || "not specified"}`,
1471
755
  ` • Plugins: ${userConfig.plugins?.length || 0}`,
1472
756
  "Output Settings:",
1473
- ` • Storage: ${userConfig.output?.storage ? `custom(${userConfig.output.storage.name})` : userConfig.output?.write === false ? "disabled" : "filesystem (default)"}`,
757
+ ` • Storage: ${userConfig.storage ? `custom(${userConfig.storage.name})` : userConfig.output?.write === false ? "disabled" : "filesystem (default)"}`,
1474
758
  ` • Formatter: ${userConfig.output?.format || "none"}`,
1475
759
  ` • Linter: ${userConfig.output?.lint || "none"}`,
1476
760
  "Environment:",
@@ -1480,7 +764,7 @@ async function setup(options) {
1480
764
  try {
1481
765
  if (isInputPath(userConfig) && !new URLPath(userConfig.input.path).isURL) {
1482
766
  await exists(userConfig.input.path);
1483
- await events.emit("debug", {
767
+ await hooks.emit("kubb:debug", {
1484
768
  date: /* @__PURE__ */ new Date(),
1485
769
  logs: [`✓ Input file validated: ${userConfig.input.path}`]
1486
770
  });
@@ -1491,12 +775,14 @@ async function setup(options) {
1491
775
  throw new Error(`Cannot read file/URL defined in \`input.path\` or set with \`kubb generate PATH\` in the CLI of your Kubb config ${userConfig.input.path}`, { cause: error });
1492
776
  }
1493
777
  }
1494
- const definedConfig = {
1495
- root: userConfig.root || process.cwd(),
778
+ if (!userConfig.adapter) throw new Error("Adapter should be defined");
779
+ const config = {
1496
780
  ...userConfig,
781
+ root: userConfig.root || process.cwd(),
782
+ parsers: userConfig.parsers ?? [],
783
+ adapter: userConfig.adapter,
1497
784
  output: {
1498
785
  write: true,
1499
- barrelType: "named",
1500
786
  extension: DEFAULT_EXTENSION,
1501
787
  defaultBanner: DEFAULT_BANNER,
1502
788
  ...userConfig.output
@@ -1507,139 +793,180 @@ async function setup(options) {
1507
793
  } : void 0,
1508
794
  plugins: userConfig.plugins
1509
795
  };
1510
- const storage = definedConfig.output.write === false ? null : definedConfig.output.storage ?? fsStorage();
1511
- if (definedConfig.output.clean) {
1512
- await events.emit("debug", {
796
+ const storage = config.output.write === false ? null : config.storage ?? fsStorage();
797
+ if (config.output.clean) {
798
+ await hooks.emit("kubb:debug", {
1513
799
  date: /* @__PURE__ */ new Date(),
1514
- logs: ["Cleaning output directories", ` • Output: ${definedConfig.output.path}`]
1515
- });
1516
- await storage?.clear(resolve(definedConfig.root, definedConfig.output.path));
1517
- }
1518
- const fabric = createFabric();
1519
- fabric.use(fsPlugin);
1520
- fabric.use(typescriptParser);
1521
- fabric.context.on("files:processing:start", (files) => {
1522
- events.emit("files:processing:start", files);
1523
- events.emit("debug", {
1524
- date: /* @__PURE__ */ new Date(),
1525
- logs: [`Writing ${files.length} files...`]
1526
- });
1527
- });
1528
- fabric.context.on("file:processing:update", async (params) => {
1529
- const { file, source } = params;
1530
- await events.emit("file:processing:update", {
1531
- ...params,
1532
- config: definedConfig,
1533
- source
1534
- });
1535
- if (source) {
1536
- const key = relative(resolve(definedConfig.root), file.path);
1537
- await storage?.setItem(key, source);
1538
- sources.set(file.path, source);
1539
- }
1540
- });
1541
- fabric.context.on("files:processing:end", async (files) => {
1542
- await events.emit("files:processing:end", files);
1543
- await events.emit("debug", {
1544
- date: /* @__PURE__ */ new Date(),
1545
- logs: [`✓ File write process completed for ${files.length} files`]
800
+ logs: ["Cleaning output directories", ` • Output: ${config.output.path}`]
1546
801
  });
802
+ await storage?.clear(resolve(config.root, config.output.path));
803
+ }
804
+ const driver = new PluginDriver(config, { hooks });
805
+ function registerMiddlewareHook(event, middlewareHooks) {
806
+ const handler = middlewareHooks[event];
807
+ if (handler) hooks.on(event, handler);
808
+ }
809
+ for (const middleware of config.middleware ?? []) for (const event of Object.keys(middleware.hooks)) registerMiddlewareHook(event, middleware.hooks);
810
+ const adapter = config.adapter;
811
+ if (!adapter) throw new Error("No adapter configured. Please provide an adapter in your kubb.config.ts.");
812
+ const source = inputToAdapterSource(config);
813
+ await hooks.emit("kubb:debug", {
814
+ date: /* @__PURE__ */ new Date(),
815
+ logs: [`Running adapter: ${adapter.name}`]
1547
816
  });
1548
- await events.emit("debug", {
817
+ driver.adapter = adapter;
818
+ driver.inputNode = await adapter.parse(source);
819
+ await hooks.emit("kubb:debug", {
1549
820
  date: /* @__PURE__ */ new Date(),
1550
821
  logs: [
1551
- "✓ Fabric initialized",
1552
- ` • Storage: ${storage ? storage.name : "disabled (dry-run)"}`,
1553
- ` • Barrel type: ${definedConfig.output.barrelType || "none"}`
822
+ `✓ Adapter '${adapter.name}' resolved InputNode`,
823
+ ` • Schemas: ${driver.inputNode.schemas.length}`,
824
+ ` • Operations: ${driver.inputNode.operations.length}`
1554
825
  ]
1555
826
  });
1556
- const pluginDriver = new PluginDriver(definedConfig, {
1557
- fabric,
1558
- events,
1559
- concurrency: 15
1560
- });
1561
- if (definedConfig.adapter) {
1562
- const source = inputToAdapterSource(definedConfig);
1563
- await events.emit("debug", {
1564
- date: /* @__PURE__ */ new Date(),
1565
- logs: [`Running adapter: ${definedConfig.adapter.name}`]
1566
- });
1567
- pluginDriver.adapter = definedConfig.adapter;
1568
- pluginDriver.rootNode = await definedConfig.adapter.parse(source);
1569
- await events.emit("debug", {
1570
- date: /* @__PURE__ */ new Date(),
1571
- logs: [
1572
- `✓ Adapter '${definedConfig.adapter.name}' resolved RootNode`,
1573
- ` • Schemas: ${pluginDriver.rootNode.schemas.length}`,
1574
- ` • Operations: ${pluginDriver.rootNode.operations.length}`
1575
- ]
1576
- });
1577
- }
1578
827
  return {
1579
- events,
1580
- fabric,
1581
- driver: pluginDriver,
1582
- sources
828
+ config,
829
+ hooks,
830
+ driver,
831
+ sources,
832
+ storage
1583
833
  };
1584
834
  }
1585
- async function build(options, overrides) {
1586
- const { fabric, files, driver, failedPlugins, pluginTimings, error, sources } = await safeBuild(options, overrides);
1587
- if (error) throw error;
1588
- if (failedPlugins.size > 0) {
1589
- const errors = [...failedPlugins].map(({ error }) => error);
1590
- throw new BuildError(`Build Error with ${failedPlugins.size} failed plugins`, { errors });
1591
- }
1592
- return {
1593
- failedPlugins,
1594
- fabric,
1595
- files,
1596
- driver,
1597
- pluginTimings,
1598
- error: void 0,
1599
- sources
835
+ /**
836
+ * Walks the AST and dispatches nodes to a plugin's direct AST hooks
837
+ * (`schema`, `operation`, `operations`).
838
+ */
839
+ async function runPluginAstHooks(plugin, context) {
840
+ const { adapter, inputNode, resolver, driver } = context;
841
+ const { exclude, include, override } = plugin.options;
842
+ if (!adapter || !inputNode) throw new Error(`[${plugin.name}] No adapter found. Add an OAS adapter (e.g. pluginOas()) before this plugin in your Kubb config.`);
843
+ function resolveRenderer(gen) {
844
+ return gen.renderer === null ? void 0 : gen.renderer ?? plugin.renderer ?? context.config.renderer;
845
+ }
846
+ const generators = plugin.generators ?? [];
847
+ const collectedOperations = [];
848
+ const generatorContext = {
849
+ ...context,
850
+ resolver: driver.getResolver(plugin.name)
1600
851
  };
852
+ await walk(inputNode, {
853
+ depth: "shallow",
854
+ async schema(node) {
855
+ const transformedNode = plugin.transformer ? transform(node, plugin.transformer) : node;
856
+ const options = resolver.resolveOptions(transformedNode, {
857
+ options: plugin.options,
858
+ exclude,
859
+ include,
860
+ override
861
+ });
862
+ if (options === null) return;
863
+ const ctx = {
864
+ ...generatorContext,
865
+ options
866
+ };
867
+ for (const gen of generators) {
868
+ if (!gen.schema) continue;
869
+ await applyHookResult(await gen.schema(transformedNode, ctx), driver, resolveRenderer(gen));
870
+ }
871
+ await driver.hooks.emit("kubb:generate:schema", transformedNode, ctx);
872
+ },
873
+ async operation(node) {
874
+ const transformedNode = plugin.transformer ? transform(node, plugin.transformer) : node;
875
+ const options = resolver.resolveOptions(transformedNode, {
876
+ options: plugin.options,
877
+ exclude,
878
+ include,
879
+ override
880
+ });
881
+ if (options !== null) {
882
+ collectedOperations.push(transformedNode);
883
+ const ctx = {
884
+ ...generatorContext,
885
+ options
886
+ };
887
+ for (const gen of generators) {
888
+ if (!gen.operation) continue;
889
+ await applyHookResult(await gen.operation(transformedNode, ctx), driver, resolveRenderer(gen));
890
+ }
891
+ await driver.hooks.emit("kubb:generate:operation", transformedNode, ctx);
892
+ }
893
+ }
894
+ });
895
+ if (collectedOperations.length > 0) {
896
+ const ctx = {
897
+ ...generatorContext,
898
+ options: plugin.options
899
+ };
900
+ for (const gen of generators) {
901
+ if (!gen.operations) continue;
902
+ await applyHookResult(await gen.operations(collectedOperations, ctx), driver, resolveRenderer(gen));
903
+ }
904
+ await driver.hooks.emit("kubb:generate:operations", collectedOperations, ctx);
905
+ }
1601
906
  }
1602
- async function safeBuild(options, overrides) {
1603
- const { fabric, driver, events, sources } = overrides ? overrides : await setup(options);
907
+ async function safeBuild(setupResult) {
908
+ const { driver, hooks, sources, storage } = setupResult;
1604
909
  const failedPlugins = /* @__PURE__ */ new Set();
1605
910
  const pluginTimings = /* @__PURE__ */ new Map();
1606
911
  const config = driver.config;
1607
912
  try {
1608
- for (const plugin of driver.plugins) {
913
+ await driver.emitSetupHooks();
914
+ if (driver.adapter && driver.inputNode) await hooks.emit("kubb:build:start", {
915
+ config,
916
+ adapter: driver.adapter,
917
+ inputNode: driver.inputNode,
918
+ getPlugin: driver.getPlugin.bind(driver),
919
+ get files() {
920
+ return driver.fileManager.files;
921
+ },
922
+ upsertFile: (...files) => driver.fileManager.upsert(...files)
923
+ });
924
+ for (const plugin of driver.plugins.values()) {
1609
925
  const context = driver.getContext(plugin);
1610
926
  const hrStart = process.hrtime();
1611
- const installer = plugin.install.bind(context);
1612
927
  try {
1613
928
  const timestamp = /* @__PURE__ */ new Date();
1614
- await events.emit("plugin:start", plugin);
1615
- await events.emit("debug", {
929
+ await hooks.emit("kubb:plugin:start", { plugin });
930
+ await hooks.emit("kubb:debug", {
1616
931
  date: timestamp,
1617
- logs: ["Installing plugin...", ` • Plugin Name: ${plugin.name}`]
932
+ logs: ["Starting plugin...", ` • Plugin Name: ${plugin.name}`]
1618
933
  });
1619
- await installer(context);
934
+ if (plugin.generators?.length || driver.hasRegisteredGenerators(plugin.name)) await runPluginAstHooks(plugin, context);
1620
935
  const duration = getElapsedMs(hrStart);
1621
936
  pluginTimings.set(plugin.name, duration);
1622
- await events.emit("plugin:end", plugin, {
937
+ await hooks.emit("kubb:plugin:end", {
938
+ plugin,
1623
939
  duration,
1624
- success: true
940
+ success: true,
941
+ config,
942
+ get files() {
943
+ return driver.fileManager.files;
944
+ },
945
+ upsertFile: (...files) => driver.fileManager.upsert(...files)
1625
946
  });
1626
- await events.emit("debug", {
947
+ await hooks.emit("kubb:debug", {
1627
948
  date: /* @__PURE__ */ new Date(),
1628
- logs: [`✓ Plugin installed successfully (${formatMs(duration)})`]
949
+ logs: [`✓ Plugin started successfully (${formatMs(duration)})`]
1629
950
  });
1630
951
  } catch (caughtError) {
1631
952
  const error = caughtError;
1632
953
  const errorTimestamp = /* @__PURE__ */ new Date();
1633
954
  const duration = getElapsedMs(hrStart);
1634
- await events.emit("plugin:end", plugin, {
955
+ await hooks.emit("kubb:plugin:end", {
956
+ plugin,
1635
957
  duration,
1636
958
  success: false,
1637
- error
959
+ error,
960
+ config,
961
+ get files() {
962
+ return driver.fileManager.files;
963
+ },
964
+ upsertFile: (...files) => driver.fileManager.upsert(...files)
1638
965
  });
1639
- await events.emit("debug", {
966
+ await hooks.emit("kubb:debug", {
1640
967
  date: errorTimestamp,
1641
968
  logs: [
1642
- "✗ Plugin installation failed",
969
+ "✗ Plugin start failed",
1643
970
  ` • Plugin Name: ${plugin.name}`,
1644
971
  ` • Error: ${error.constructor.name} - ${error.message}`,
1645
972
  " • Stack Trace:",
@@ -1652,50 +979,56 @@ async function safeBuild(options, overrides) {
1652
979
  });
1653
980
  }
1654
981
  }
1655
- if (config.output.barrelType) {
1656
- const rootPath = resolve(resolve(config.root), config.output.path, BARREL_FILENAME);
1657
- const rootDir = dirname(rootPath);
1658
- await events.emit("debug", {
1659
- date: /* @__PURE__ */ new Date(),
1660
- logs: [
1661
- "Generating barrel file",
1662
- ` • Type: ${config.output.barrelType}`,
1663
- ` • Path: ${rootPath}`
1664
- ]
1665
- });
1666
- const barrelFiles = fabric.files.filter((file) => {
1667
- return file.sources.some((source) => source.isIndexable);
1668
- });
1669
- await events.emit("debug", {
1670
- date: /* @__PURE__ */ new Date(),
1671
- logs: [`Found ${barrelFiles.length} indexable files for barrel export`]
1672
- });
1673
- const existingBarrel = fabric.files.find((f) => f.path === rootPath);
1674
- const rootFile = {
1675
- path: rootPath,
1676
- baseName: BARREL_FILENAME,
1677
- exports: buildBarrelExports({
1678
- barrelFiles,
1679
- rootDir,
1680
- existingExports: new Set(existingBarrel?.exports?.flatMap((e) => Array.isArray(e.name) ? e.name : [e.name]).filter((n) => Boolean(n)) ?? []),
1681
- config,
1682
- driver
1683
- }),
1684
- sources: [],
1685
- imports: [],
1686
- meta: {}
1687
- };
1688
- await fabric.upsertFile(rootFile);
1689
- await events.emit("debug", {
1690
- date: /* @__PURE__ */ new Date(),
1691
- logs: [`✓ Generated barrel file (${rootFile.exports?.length || 0} exports)`]
1692
- });
1693
- }
1694
- const files = [...fabric.files];
1695
- await fabric.write({ extension: config.output.extension });
982
+ await hooks.emit("kubb:plugins:end", {
983
+ config,
984
+ get files() {
985
+ return driver.fileManager.files;
986
+ },
987
+ upsertFile: (...files) => driver.fileManager.upsert(...files)
988
+ });
989
+ const files = driver.fileManager.files;
990
+ const parsersMap = /* @__PURE__ */ new Map();
991
+ for (const parser of config.parsers) if (parser.extNames) for (const extname of parser.extNames) parsersMap.set(extname, parser);
992
+ const fileProcessor = new FileProcessor();
993
+ await hooks.emit("kubb:debug", {
994
+ date: /* @__PURE__ */ new Date(),
995
+ logs: [`Writing ${files.length} files...`]
996
+ });
997
+ await fileProcessor.run(files, {
998
+ parsers: parsersMap,
999
+ extension: config.output.extension,
1000
+ onStart: async (processingFiles) => {
1001
+ await hooks.emit("kubb:files:processing:start", { files: processingFiles });
1002
+ },
1003
+ onUpdate: async ({ file, source, processed, total, percentage }) => {
1004
+ await hooks.emit("kubb:file:processing:update", {
1005
+ file,
1006
+ source,
1007
+ processed,
1008
+ total,
1009
+ percentage,
1010
+ config
1011
+ });
1012
+ if (source) {
1013
+ await storage?.setItem(file.path, source);
1014
+ sources.set(file.path, source);
1015
+ }
1016
+ },
1017
+ onEnd: async (processedFiles) => {
1018
+ await hooks.emit("kubb:files:processing:end", { files: processedFiles });
1019
+ await hooks.emit("kubb:debug", {
1020
+ date: /* @__PURE__ */ new Date(),
1021
+ logs: [`✓ File write process completed for ${processedFiles.length} files`]
1022
+ });
1023
+ }
1024
+ });
1025
+ await hooks.emit("kubb:build:end", {
1026
+ files,
1027
+ config,
1028
+ outputDir: resolve(config.root, config.output.path)
1029
+ });
1696
1030
  return {
1697
1031
  failedPlugins,
1698
- fabric,
1699
1032
  files,
1700
1033
  driver,
1701
1034
  pluginTimings,
@@ -1704,199 +1037,255 @@ async function safeBuild(options, overrides) {
1704
1037
  } catch (error) {
1705
1038
  return {
1706
1039
  failedPlugins,
1707
- fabric,
1708
1040
  files: [],
1709
1041
  driver,
1710
1042
  pluginTimings,
1711
1043
  error,
1712
1044
  sources
1713
1045
  };
1046
+ } finally {
1047
+ driver.dispose();
1714
1048
  }
1715
1049
  }
1716
- function buildBarrelExports({ barrelFiles, rootDir, existingExports, config, driver }) {
1717
- const pluginNameMap = /* @__PURE__ */ new Map();
1718
- for (const plugin of driver.plugins) pluginNameMap.set(plugin.name, plugin);
1719
- return barrelFiles.flatMap((file) => {
1720
- const containsOnlyTypes = file.sources?.every((source) => source.isTypeOnly);
1721
- return (file.sources ?? []).flatMap((source) => {
1722
- if (!file.path || !source.isIndexable) return [];
1723
- const meta = file.meta;
1724
- const pluginOptions = (meta?.pluginName ? pluginNameMap.get(meta.pluginName) : void 0)?.options;
1725
- if (!pluginOptions || pluginOptions.output?.barrelType === false) return [];
1726
- const exportName = config.output.barrelType === "all" ? void 0 : source.name ? [source.name] : void 0;
1727
- if (exportName?.some((n) => existingExports.has(n))) return [];
1728
- return [{
1729
- name: exportName,
1730
- path: getRelativePath(rootDir, file.path),
1731
- isTypeOnly: config.output.barrelType === "all" ? containsOnlyTypes : source.isTypeOnly
1732
- }];
1733
- });
1734
- });
1050
+ async function build(setupResult) {
1051
+ const { files, driver, failedPlugins, pluginTimings, error, sources } = await safeBuild(setupResult);
1052
+ if (error) throw error;
1053
+ if (failedPlugins.size > 0) {
1054
+ const errors = [...failedPlugins].map(({ error }) => error);
1055
+ throw new BuildError(`Build Error with ${failedPlugins.size} failed plugins`, { errors });
1056
+ }
1057
+ return {
1058
+ failedPlugins,
1059
+ files,
1060
+ driver,
1061
+ pluginTimings,
1062
+ error: void 0,
1063
+ sources
1064
+ };
1735
1065
  }
1736
- /**
1737
- * Maps the resolved `Config['input']` shape into an `AdapterSource` that
1738
- * the adapter's `parse()` can consume.
1739
- */
1740
1066
  function inputToAdapterSource(config) {
1741
1067
  if (Array.isArray(config.input)) return {
1742
1068
  type: "paths",
1743
- paths: config.input.map((i) => resolve(config.root, i.path))
1069
+ paths: config.input.map((i) => new URLPath(i.path).isURL ? i.path : resolve(config.root, i.path))
1744
1070
  };
1745
1071
  if ("data" in config.input) return {
1746
1072
  type: "data",
1747
1073
  data: config.input.data
1748
1074
  };
1075
+ if (new URLPath(config.input.path).isURL) return {
1076
+ type: "path",
1077
+ path: config.input.path
1078
+ };
1749
1079
  return {
1750
1080
  type: "path",
1751
1081
  path: resolve(config.root, config.input.path)
1752
1082
  };
1753
1083
  }
1754
- //#endregion
1755
- //#region src/defineAdapter.ts
1756
1084
  /**
1757
- * Wraps an adapter builder to make the options parameter optional.
1085
+ * Creates a Kubb instance bound to a single config entry.
1086
+ *
1087
+ * Accepts a user-facing config shape and resolves it to a full {@link Config} during
1088
+ * `setup()`. The instance then holds shared state (`hooks`, `sources`, `driver`, `config`)
1089
+ * across the `setup → build` lifecycle. Attach event listeners to `kubb.hooks` before
1090
+ * calling `setup()` or `build()`.
1758
1091
  *
1759
1092
  * @example
1760
1093
  * ```ts
1761
- * export const adapterOas = defineAdapter<OasAdapter>((options) => {
1762
- * const { validate = true, dateType = 'string' } = options
1763
- * return {
1764
- * name: adapterOasName,
1765
- * options: { validate, dateType, ... },
1766
- * parse(source) { ... },
1767
- * }
1094
+ * const kubb = createKubb(userConfig)
1095
+ *
1096
+ * kubb.hooks.on('kubb:plugin:end', ({ plugin, duration }) => {
1097
+ * console.log(`${plugin.name} completed in ${duration}ms`)
1768
1098
  * })
1099
+ *
1100
+ * const { files, failedPlugins } = await kubb.safeBuild()
1769
1101
  * ```
1770
1102
  */
1771
- function defineAdapter(build) {
1772
- return (options) => build(options ?? {});
1773
- }
1774
- //#endregion
1775
- //#region src/defineGenerator.ts
1776
- function defineGenerator(generator) {
1777
- if (generator.type === "react") return {
1778
- version: "2",
1779
- Operations() {
1780
- return null;
1103
+ function createKubb(userConfig, options = {}) {
1104
+ const hooks = options.hooks ?? new AsyncEventEmitter();
1105
+ let setupResult;
1106
+ const instance = {
1107
+ get hooks() {
1108
+ return hooks;
1781
1109
  },
1782
- Operation() {
1783
- return null;
1110
+ get sources() {
1111
+ return setupResult?.sources ?? /* @__PURE__ */ new Map();
1784
1112
  },
1785
- Schema() {
1786
- return null;
1113
+ get driver() {
1114
+ return setupResult?.driver;
1787
1115
  },
1788
- ...generator
1789
- };
1790
- return {
1791
- version: "2",
1792
- async operations() {
1793
- return [];
1116
+ get config() {
1117
+ return setupResult?.config;
1794
1118
  },
1795
- async operation() {
1796
- return [];
1119
+ async setup() {
1120
+ setupResult = await setup(userConfig, { hooks });
1797
1121
  },
1798
- async schema() {
1799
- return [];
1122
+ async build() {
1123
+ if (!setupResult) await instance.setup();
1124
+ return build(setupResult);
1800
1125
  },
1801
- ...generator
1126
+ async safeBuild() {
1127
+ if (!setupResult) await instance.setup();
1128
+ return safeBuild(setupResult);
1129
+ }
1802
1130
  };
1131
+ return instance;
1132
+ }
1133
+ //#endregion
1134
+ //#region src/createRenderer.ts
1135
+ /**
1136
+ * Creates a renderer factory for use in generator definitions.
1137
+ *
1138
+ * Wrap your renderer factory function with this helper to register it as the
1139
+ * renderer for a generator. Core will call this factory once per render cycle
1140
+ * to obtain a fresh renderer instance.
1141
+ *
1142
+ * @example
1143
+ * ```ts
1144
+ * // packages/renderer-jsx/src/index.ts
1145
+ * export const jsxRenderer = createRenderer(() => {
1146
+ * const runtime = new Runtime()
1147
+ * return {
1148
+ * async render(element) { await runtime.render(element) },
1149
+ * get files() { return runtime.nodes },
1150
+ * unmount(error) { runtime.unmount(error) },
1151
+ * }
1152
+ * })
1153
+ *
1154
+ * // packages/plugin-zod/src/generators/zodGenerator.tsx
1155
+ * import { jsxRenderer } from '@kubb/renderer-jsx'
1156
+ * export const zodGenerator = defineGenerator<PluginZod>({
1157
+ * name: 'zod',
1158
+ * renderer: jsxRenderer,
1159
+ * schema(node, options) { return <File ...>...</File> },
1160
+ * })
1161
+ * ```
1162
+ */
1163
+ function createRenderer(factory) {
1164
+ return factory;
1165
+ }
1166
+ //#endregion
1167
+ //#region src/defineGenerator.ts
1168
+ /**
1169
+ * Defines a generator. Returns the object as-is with correct `this` typings.
1170
+ * `applyHookResult` handles renderer elements and `File[]` uniformly using
1171
+ * the generator's declared `renderer` factory.
1172
+ */
1173
+ function defineGenerator(generator) {
1174
+ return generator;
1803
1175
  }
1804
1176
  //#endregion
1805
1177
  //#region src/defineLogger.ts
1178
+ /**
1179
+ * Wraps a logger definition into a typed {@link Logger}.
1180
+ *
1181
+ * @example
1182
+ * export const myLogger = defineLogger({
1183
+ * name: 'my-logger',
1184
+ * install(context, options) {
1185
+ * context.on('kubb:info', (message) => console.log('ℹ', message))
1186
+ * context.on('kubb:error', (error) => console.error('✗', error.message))
1187
+ * },
1188
+ * })
1189
+ */
1806
1190
  function defineLogger(logger) {
1807
- return { ...logger };
1191
+ return logger;
1808
1192
  }
1809
1193
  //#endregion
1810
- //#region src/definePlugin.ts
1194
+ //#region src/defineMiddleware.ts
1811
1195
  /**
1812
- * Wraps a plugin builder to make the options parameter optional.
1196
+ * Creates a middleware factory using the hook-style (`hooks:`) API.
1197
+ *
1198
+ * Mirrors `definePlugin`: the factory is called with optional options and returns a
1199
+ * fresh `Middleware` instance. Placing per-build state (e.g. accumulators) inside the
1200
+ * factory closure ensures each `createKubb` invocation gets its own isolated instance.
1201
+ *
1202
+ * @example
1203
+ * ```ts
1204
+ * // Stateless middleware
1205
+ * export const logMiddleware = defineMiddleware(() => ({
1206
+ * name: 'log-middleware',
1207
+ * hooks: {
1208
+ * 'kubb:build:end'({ files }) {
1209
+ * console.log(`Build complete with ${files.length} files`)
1210
+ * },
1211
+ * },
1212
+ * }))
1213
+ *
1214
+ * // Middleware with options and per-build state
1215
+ * export const myMiddleware = defineMiddleware((options: { prefix: string } = { prefix: '' }) => {
1216
+ * const seen = new Set<string>()
1217
+ * return {
1218
+ * name: 'my-middleware',
1219
+ * hooks: {
1220
+ * 'kubb:plugin:end'({ plugin }) {
1221
+ * seen.add(`${options.prefix}${plugin.name}`)
1222
+ * },
1223
+ * },
1224
+ * }
1225
+ * })
1226
+ *
1227
+ * // Usage in kubb.config.ts:
1228
+ * export default defineConfig({
1229
+ * middleware: [logMiddleware(), myMiddleware({ prefix: 'pfx:' })],
1230
+ * })
1231
+ * ```
1813
1232
  */
1814
- function definePlugin(build) {
1815
- return (options) => build(options ?? {});
1233
+ function defineMiddleware(factory) {
1234
+ return (options) => factory(options ?? {});
1816
1235
  }
1817
1236
  //#endregion
1818
- //#region src/PackageManager.ts
1819
- var PackageManager = class PackageManager {
1820
- static #cache = {};
1821
- #cwd;
1822
- constructor(workspace) {
1823
- if (workspace) this.#cwd = workspace;
1824
- }
1825
- set workspace(workspace) {
1826
- this.#cwd = workspace;
1827
- }
1828
- get workspace() {
1829
- return this.#cwd;
1830
- }
1831
- normalizeDirectory(directory) {
1832
- const lastChar = directory[directory.length - 1];
1833
- if (lastChar && !PATH_SEPARATORS.includes(lastChar)) return `${directory}/`;
1834
- return directory;
1835
- }
1836
- getLocation(path) {
1837
- let location = path;
1838
- if (this.#cwd) location = mod.createRequire(this.normalizeDirectory(this.#cwd)).resolve(path);
1839
- return location;
1840
- }
1841
- async import(path) {
1842
- let location = this.getLocation(path);
1843
- if (os.platform() === "win32") location = pathToFileURL(location).href;
1844
- const module = await import(location);
1845
- return module?.default ?? module;
1846
- }
1847
- async getPackageJSON() {
1848
- const pkgPath = pkg.up({ cwd: this.#cwd });
1849
- if (!pkgPath) return;
1850
- const json = await read(pkgPath);
1851
- return JSON.parse(json);
1852
- }
1853
- getPackageJSONSync() {
1854
- const pkgPath = pkg.up({ cwd: this.#cwd });
1855
- if (!pkgPath) return;
1856
- const json = readSync(pkgPath);
1857
- return JSON.parse(json);
1858
- }
1859
- static setVersion(dependency, version) {
1860
- PackageManager.#cache[dependency] = version;
1861
- }
1862
- #match(packageJSON, dependency) {
1863
- const dependencies = {
1864
- ...packageJSON.dependencies || {},
1865
- ...packageJSON.devDependencies || {}
1866
- };
1867
- if (typeof dependency === "string" && dependencies[dependency]) return dependencies[dependency];
1868
- const matchedDependency = Object.keys(dependencies).find((dep) => dep.match(dependency));
1869
- return matchedDependency ? dependencies[matchedDependency] : void 0;
1870
- }
1871
- async getVersion(dependency) {
1872
- if (typeof dependency === "string" && PackageManager.#cache[dependency]) return PackageManager.#cache[dependency];
1873
- const packageJSON = await this.getPackageJSON();
1874
- if (!packageJSON) return;
1875
- return this.#match(packageJSON, dependency);
1876
- }
1877
- getVersionSync(dependency) {
1878
- if (typeof dependency === "string" && PackageManager.#cache[dependency]) return PackageManager.#cache[dependency];
1879
- const packageJSON = this.getPackageJSONSync();
1880
- if (!packageJSON) return;
1881
- return this.#match(packageJSON, dependency);
1882
- }
1883
- async isValid(dependency, version) {
1884
- const packageVersion = await this.getVersion(dependency);
1885
- if (!packageVersion) return false;
1886
- if (packageVersion === version) return true;
1887
- const semVer = coerce(packageVersion);
1888
- if (!semVer) return false;
1889
- return satisfies(semVer, version);
1890
- }
1891
- isValidSync(dependency, version) {
1892
- const packageVersion = this.getVersionSync(dependency);
1893
- if (!packageVersion) return false;
1894
- if (packageVersion === version) return true;
1895
- const semVer = coerce(packageVersion);
1896
- if (!semVer) return false;
1897
- return satisfies(semVer, version);
1898
- }
1899
- };
1237
+ //#region src/defineParser.ts
1238
+ /**
1239
+ * Defines a parser with type safety.
1240
+ *
1241
+ * Use this function to create parsers that transform generated files to strings
1242
+ * based on their extension.
1243
+ *
1244
+ * @example
1245
+ * ```ts
1246
+ * import { defineParser } from '@kubb/core'
1247
+ *
1248
+ * export const jsonParser = defineParser({
1249
+ * name: 'json',
1250
+ * extNames: ['.json'],
1251
+ * parse(file) {
1252
+ * const { extractStringsFromNodes } = await import('@kubb/ast')
1253
+ * return file.sources.map((s) => extractStringsFromNodes(s.nodes ?? [])).join('\n')
1254
+ * },
1255
+ * })
1256
+ * ```
1257
+ */
1258
+ function defineParser(parser) {
1259
+ return parser;
1260
+ }
1261
+ //#endregion
1262
+ //#region src/definePlugin.ts
1263
+ /**
1264
+ * Creates a plugin factory using the hook-style (`hooks:`) API.
1265
+ *
1266
+ * The returned factory is called with optional options and produces a `Plugin`
1267
+ * that coexists with plugins created via the legacy `createPlugin` API in the same
1268
+ * `kubb.config.ts`.
1269
+ *
1270
+ * Lifecycle handlers are registered on the `PluginDriver`'s `AsyncEventEmitter`, enabling
1271
+ * both the plugin's own handlers and external tooling (CLI, devtools) to observe every event.
1272
+ *
1273
+ * @example
1274
+ * ```ts
1275
+ * // With PluginFactoryOptions (recommended for real plugins)
1276
+ * export const pluginTs = definePlugin<PluginTs>((options) => ({
1277
+ * name: 'plugin-ts',
1278
+ * hooks: {
1279
+ * 'kubb:plugin:setup'(ctx) {
1280
+ * ctx.setResolver(resolverTs) // typed as Partial<ResolverTs>
1281
+ * },
1282
+ * },
1283
+ * }))
1284
+ * ```
1285
+ */
1286
+ function definePlugin(factory) {
1287
+ return (options) => factory(options ?? {});
1288
+ }
1900
1289
  //#endregion
1901
1290
  //#region src/storages/memoryStorage.ts
1902
1291
  /**
@@ -1908,15 +1297,17 @@ var PackageManager = class PackageManager {
1908
1297
  *
1909
1298
  * @example
1910
1299
  * ```ts
1911
- * import { defineConfig, memoryStorage } from '@kubb/core'
1300
+ * import { memoryStorage } from '@kubb/core'
1301
+ * import { defineConfig } from 'kubb'
1912
1302
  *
1913
1303
  * export default defineConfig({
1914
1304
  * input: { path: './petStore.yaml' },
1915
- * output: { path: './src/gen', storage: memoryStorage() },
1305
+ * output: { path: './src/gen' },
1306
+ * storage: memoryStorage(),
1916
1307
  * })
1917
1308
  * ```
1918
1309
  */
1919
- const memoryStorage = defineStorage(() => {
1310
+ const memoryStorage = createStorage(() => {
1920
1311
  const store = /* @__PURE__ */ new Map();
1921
1312
  return {
1922
1313
  name: "memory",
@@ -1946,421 +1337,6 @@ const memoryStorage = defineStorage(() => {
1946
1337
  };
1947
1338
  });
1948
1339
  //#endregion
1949
- //#region src/utils/FunctionParams.ts
1950
- /**
1951
- * @deprecated
1952
- */
1953
- var FunctionParams = class FunctionParams {
1954
- #items = [];
1955
- get items() {
1956
- return this.#items.flat();
1957
- }
1958
- add(item) {
1959
- if (!item) return this;
1960
- if (Array.isArray(item)) {
1961
- item.filter((x) => x !== void 0).forEach((it) => {
1962
- this.#items.push(it);
1963
- });
1964
- return this;
1965
- }
1966
- this.#items.push(item);
1967
- return this;
1968
- }
1969
- static #orderItems(items) {
1970
- return sortBy(items.filter(Boolean), [(item) => Array.isArray(item), "desc"], [(item) => !Array.isArray(item) && item.default !== void 0, "asc"], [(item) => Array.isArray(item) || (item.required ?? true), "desc"]);
1971
- }
1972
- static #addParams(acc, item) {
1973
- const { enabled = true, name, type, required = true, ...rest } = item;
1974
- if (!enabled) return acc;
1975
- if (!name) {
1976
- acc.push(`${type}${rest.default ? ` = ${rest.default}` : ""}`);
1977
- return acc;
1978
- }
1979
- const parameterName = name.startsWith("{") ? name : camelCase(name);
1980
- if (type) if (required) acc.push(`${parameterName}: ${type}${rest.default ? ` = ${rest.default}` : ""}`);
1981
- else acc.push(`${parameterName}?: ${type}`);
1982
- else acc.push(`${parameterName}`);
1983
- return acc;
1984
- }
1985
- static toObject(items) {
1986
- let type = [];
1987
- let name = [];
1988
- const enabled = items.every((item) => item.enabled) ? items.at(0)?.enabled : true;
1989
- const required = items.every((item) => item.required) ?? true;
1990
- items.forEach((item) => {
1991
- name = FunctionParams.#addParams(name, {
1992
- ...item,
1993
- type: void 0
1994
- });
1995
- if (items.some((item) => item.type)) type = FunctionParams.#addParams(type, item);
1996
- });
1997
- return {
1998
- name: `{ ${name.join(", ")} }`,
1999
- type: type.length ? `{ ${type.join("; ")} }` : void 0,
2000
- enabled,
2001
- required
2002
- };
2003
- }
2004
- toObject() {
2005
- const items = FunctionParams.#orderItems(this.#items).flat();
2006
- return FunctionParams.toObject(items);
2007
- }
2008
- static toString(items) {
2009
- return FunctionParams.#orderItems(items).reduce((acc, item) => {
2010
- if (Array.isArray(item)) {
2011
- if (item.length <= 0) return acc;
2012
- const subItems = FunctionParams.#orderItems(item);
2013
- const objectItem = FunctionParams.toObject(subItems);
2014
- return FunctionParams.#addParams(acc, objectItem);
2015
- }
2016
- return FunctionParams.#addParams(acc, item);
2017
- }, []).join(", ");
2018
- }
2019
- toString() {
2020
- const items = FunctionParams.#orderItems(this.#items);
2021
- return FunctionParams.toString(items);
2022
- }
2023
- };
2024
- //#endregion
2025
- //#region src/utils/formatters.ts
2026
- /**
2027
- * Check if a formatter command is available in the system.
2028
- *
2029
- * @param formatter - The formatter to check ('biome', 'prettier', or 'oxfmt')
2030
- * @returns Promise that resolves to true if the formatter is available, false otherwise
2031
- *
2032
- * @remarks
2033
- * This function checks availability by running `<formatter> --version` command.
2034
- * All supported formatters (biome, prettier, oxfmt) implement the --version flag.
2035
- */
2036
- async function isFormatterAvailable(formatter) {
2037
- try {
2038
- await x(formatter, ["--version"], { nodeOptions: { stdio: "ignore" } });
2039
- return true;
2040
- } catch {
2041
- return false;
2042
- }
2043
- }
2044
- /**
2045
- * Detect which formatter is available in the system.
2046
- *
2047
- * @returns Promise that resolves to the first available formatter or undefined if none are found
2048
- *
2049
- * @remarks
2050
- * Checks in order of preference: biome, oxfmt, prettier.
2051
- * Uses the `--version` flag to detect if each formatter command is available.
2052
- * This is a reliable method as all supported formatters implement this flag.
2053
- *
2054
- * @example
2055
- * ```typescript
2056
- * const formatter = await detectFormatter()
2057
- * if (formatter) {
2058
- * console.log(`Using ${formatter} for formatting`)
2059
- * } else {
2060
- * console.log('No formatter found')
2061
- * }
2062
- * ```
2063
- */
2064
- async function detectFormatter() {
2065
- for (const formatter of [
2066
- "biome",
2067
- "oxfmt",
2068
- "prettier"
2069
- ]) if (await isFormatterAvailable(formatter)) return formatter;
2070
- }
2071
- //#endregion
2072
- //#region src/utils/TreeNode.ts
2073
- var TreeNode = class TreeNode {
2074
- data;
2075
- parent;
2076
- children = [];
2077
- #cachedLeaves = void 0;
2078
- constructor(data, parent) {
2079
- this.data = data;
2080
- this.parent = parent;
2081
- }
2082
- addChild(data) {
2083
- const child = new TreeNode(data, this);
2084
- if (!this.children) this.children = [];
2085
- this.children.push(child);
2086
- return child;
2087
- }
2088
- get root() {
2089
- if (!this.parent) return this;
2090
- return this.parent.root;
2091
- }
2092
- get leaves() {
2093
- if (!this.children || this.children.length === 0) return [this];
2094
- if (this.#cachedLeaves) return this.#cachedLeaves;
2095
- const leaves = [];
2096
- for (const child of this.children) leaves.push(...child.leaves);
2097
- this.#cachedLeaves = leaves;
2098
- return leaves;
2099
- }
2100
- forEach(callback) {
2101
- if (typeof callback !== "function") throw new TypeError("forEach() callback must be a function");
2102
- callback(this);
2103
- for (const child of this.children) child.forEach(callback);
2104
- return this;
2105
- }
2106
- findDeep(predicate) {
2107
- if (typeof predicate !== "function") throw new TypeError("find() predicate must be a function");
2108
- return this.leaves.find(predicate);
2109
- }
2110
- forEachDeep(callback) {
2111
- if (typeof callback !== "function") throw new TypeError("forEach() callback must be a function");
2112
- this.leaves.forEach(callback);
2113
- }
2114
- filterDeep(callback) {
2115
- if (typeof callback !== "function") throw new TypeError("filter() callback must be a function");
2116
- return this.leaves.filter(callback);
2117
- }
2118
- mapDeep(callback) {
2119
- if (typeof callback !== "function") throw new TypeError("map() callback must be a function");
2120
- return this.leaves.map(callback);
2121
- }
2122
- static build(files, root) {
2123
- try {
2124
- const filteredTree = buildDirectoryTree(files, root);
2125
- if (!filteredTree) return null;
2126
- const treeNode = new TreeNode({
2127
- name: filteredTree.name,
2128
- path: filteredTree.path,
2129
- file: filteredTree.file,
2130
- type: getMode(filteredTree.path)
2131
- });
2132
- const recurse = (node, item) => {
2133
- const subNode = node.addChild({
2134
- name: item.name,
2135
- path: item.path,
2136
- file: item.file,
2137
- type: getMode(item.path)
2138
- });
2139
- if (item.children?.length) item.children?.forEach((child) => {
2140
- recurse(subNode, child);
2141
- });
2142
- };
2143
- filteredTree.children?.forEach((child) => {
2144
- recurse(treeNode, child);
2145
- });
2146
- return treeNode;
2147
- } catch (error) {
2148
- throw new Error("Something went wrong with creating barrel files with the TreeNode class", { cause: error });
2149
- }
2150
- }
2151
- };
2152
- const normalizePath = (p) => p.replaceAll("\\", "/");
2153
- function buildDirectoryTree(files, rootFolder = "") {
2154
- const normalizedRootFolder = normalizePath(rootFolder);
2155
- const rootPrefix = normalizedRootFolder.endsWith("/") ? normalizedRootFolder : `${normalizedRootFolder}/`;
2156
- const filteredFiles = files.filter((file) => {
2157
- const normalizedFilePath = normalizePath(file.path);
2158
- return rootFolder ? normalizedFilePath.startsWith(rootPrefix) && !normalizedFilePath.endsWith(".json") : !normalizedFilePath.endsWith(".json");
2159
- });
2160
- if (filteredFiles.length === 0) return null;
2161
- const root = {
2162
- name: rootFolder || "",
2163
- path: rootFolder || "",
2164
- children: []
2165
- };
2166
- filteredFiles.forEach((file) => {
2167
- const parts = file.path.slice(rootFolder.length).split("/").filter(Boolean);
2168
- let currentLevel = root.children;
2169
- let currentPath = normalizePath(rootFolder);
2170
- parts.forEach((part, index) => {
2171
- currentPath = path.posix.join(currentPath, part);
2172
- let existingNode = currentLevel.find((node) => node.name === part);
2173
- if (!existingNode) {
2174
- if (index === parts.length - 1) existingNode = {
2175
- name: part,
2176
- file,
2177
- path: currentPath
2178
- };
2179
- else existingNode = {
2180
- name: part,
2181
- path: currentPath,
2182
- children: []
2183
- };
2184
- currentLevel.push(existingNode);
2185
- }
2186
- if (!existingNode.file) currentLevel = existingNode.children;
2187
- });
2188
- });
2189
- return root;
2190
- }
2191
- //#endregion
2192
- //#region src/utils/getBarrelFiles.ts
2193
- /** biome-ignore-all lint/suspicious/useIterableCallbackReturn: not needed */
2194
- function getBarrelFilesByRoot(root, files) {
2195
- const cachedFiles = /* @__PURE__ */ new Map();
2196
- TreeNode.build(files, root)?.forEach((treeNode) => {
2197
- if (!treeNode || !treeNode.children || !treeNode.parent?.data.path) return;
2198
- const barrelFile = {
2199
- path: join(treeNode.parent?.data.path, "index.ts"),
2200
- baseName: "index.ts",
2201
- exports: [],
2202
- imports: [],
2203
- sources: []
2204
- };
2205
- const previousBarrelFile = cachedFiles.get(barrelFile.path);
2206
- treeNode.leaves.forEach((item) => {
2207
- if (!item.data.name) return;
2208
- (item.data.file?.sources || []).forEach((source) => {
2209
- if (!item.data.file?.path || !source.isIndexable || !source.name) return;
2210
- if (previousBarrelFile?.sources.some((item) => item.name === source.name && item.isTypeOnly === source.isTypeOnly)) return;
2211
- barrelFile.exports.push({
2212
- name: [source.name],
2213
- path: getRelativePath(treeNode.parent?.data.path, item.data.path),
2214
- isTypeOnly: source.isTypeOnly
2215
- });
2216
- barrelFile.sources.push({
2217
- name: source.name,
2218
- isTypeOnly: source.isTypeOnly,
2219
- value: "",
2220
- isExportable: false,
2221
- isIndexable: false
2222
- });
2223
- });
2224
- });
2225
- if (previousBarrelFile) {
2226
- previousBarrelFile.sources.push(...barrelFile.sources);
2227
- previousBarrelFile.exports?.push(...barrelFile.exports || []);
2228
- } else cachedFiles.set(barrelFile.path, barrelFile);
2229
- });
2230
- return [...cachedFiles.values()];
2231
- }
2232
- function trimExtName(text) {
2233
- const dotIndex = text.lastIndexOf(".");
2234
- if (dotIndex > 0 && !text.includes("/", dotIndex)) return text.slice(0, dotIndex);
2235
- return text;
2236
- }
2237
- async function getBarrelFiles(files, { type, meta = {}, root, output }) {
2238
- if (!type || type === "propagate") return [];
2239
- const pathToBuildFrom = join(root, output.path);
2240
- if (trimExtName(pathToBuildFrom).endsWith("index")) return [];
2241
- const barrelFiles = getBarrelFilesByRoot(pathToBuildFrom, files);
2242
- if (type === "all") return barrelFiles.map((file) => {
2243
- return {
2244
- ...file,
2245
- exports: file.exports?.map((exportItem) => {
2246
- return {
2247
- ...exportItem,
2248
- name: void 0
2249
- };
2250
- })
2251
- };
2252
- });
2253
- return barrelFiles.map((indexFile) => {
2254
- return {
2255
- ...indexFile,
2256
- meta
2257
- };
2258
- });
2259
- }
2260
- //#endregion
2261
- //#region src/utils/getPlugins.ts
2262
- function isJSONPlugins(plugins) {
2263
- return Array.isArray(plugins) && plugins.some((plugin) => Array.isArray(plugin) && typeof plugin[0] === "string");
2264
- }
2265
- function isObjectPlugins(plugins) {
2266
- return plugins instanceof Object && !Array.isArray(plugins);
2267
- }
2268
- function getPlugins(plugins) {
2269
- if (isObjectPlugins(plugins)) throw new Error("Object plugins are not supported anymore, best to use http://kubb.dev/getting-started/configure#json");
2270
- if (isJSONPlugins(plugins)) throw new Error("JSON plugins are not supported anymore, best to use http://kubb.dev/getting-started/configure#json");
2271
- return Promise.resolve(plugins);
2272
- }
2273
- //#endregion
2274
- //#region src/utils/getConfigs.ts
2275
- /**
2276
- * Converting UserConfig to Config Array without a change in the object beside the JSON convert.
2277
- */
2278
- async function getConfigs(config, args) {
2279
- let userConfigs = await (typeof config === "function" ? Promise.resolve(config(args)) : Promise.resolve(config));
2280
- if (!Array.isArray(userConfigs)) userConfigs = [userConfigs];
2281
- const results = [];
2282
- for (const item of userConfigs) {
2283
- const plugins = item.plugins ? await getPlugins(item.plugins) : void 0;
2284
- results.push({
2285
- ...item,
2286
- plugins
2287
- });
2288
- }
2289
- return results;
2290
- }
2291
- //#endregion
2292
- //#region src/utils/linters.ts
2293
- async function isLinterAvailable(linter) {
2294
- try {
2295
- await x(linter, ["--version"], { nodeOptions: { stdio: "ignore" } });
2296
- return true;
2297
- } catch {
2298
- return false;
2299
- }
2300
- }
2301
- async function detectLinter() {
2302
- for (const linter of [
2303
- "biome",
2304
- "oxlint",
2305
- "eslint"
2306
- ]) if (await isLinterAvailable(linter)) return linter;
2307
- }
2308
- //#endregion
2309
- //#region src/utils/resolveOptions.ts
2310
- function matchesOperationPattern(node, type, pattern) {
2311
- switch (type) {
2312
- case "tag": return node.tags.some((tag) => !!tag.match(pattern));
2313
- case "operationId": return !!node.operationId.match(pattern);
2314
- case "path": return !!node.path.match(pattern);
2315
- case "method": return !!node.method.toLowerCase().match(pattern);
2316
- default: return false;
2317
- }
2318
- }
2319
- function matchesSchemaPattern(node, type, pattern) {
2320
- switch (type) {
2321
- case "schemaName": return node.name ? !!node.name.match(pattern) : false;
2322
- default: return null;
2323
- }
2324
- }
2325
- /**
2326
- * Resolves the effective plugin options for a given AST node by applying
2327
- * `exclude`, `include`, and `override` rules from the plugin configuration.
2328
- *
2329
- * Returns `null` when the node is excluded or not matched by `include`.
2330
- * Returns the merged options (base options merged with any matching `override`) otherwise.
2331
- *
2332
- * Supported filter types for `OperationNode`: `tag`, `operationId`, `path`, `method`.
2333
- * Supported filter types for `SchemaNode`: `schemaName`.
2334
- *
2335
- * @example
2336
- * const resolved = resolveOptions(operationNode, { options, exclude, include, override })
2337
- * if (!resolved) return // excluded
2338
- */
2339
- function resolveOptions(node, { options, exclude = [], include, override = [] }) {
2340
- if (isOperationNode(node)) {
2341
- if (exclude.some(({ type, pattern }) => matchesOperationPattern(node, type, pattern))) return null;
2342
- if (include && !include.some(({ type, pattern }) => matchesOperationPattern(node, type, pattern))) return null;
2343
- const overrideOptions = override.find(({ type, pattern }) => matchesOperationPattern(node, type, pattern))?.options;
2344
- return {
2345
- ...options,
2346
- ...overrideOptions
2347
- };
2348
- }
2349
- if (isSchemaNode(node)) {
2350
- if (exclude.some(({ type, pattern }) => matchesSchemaPattern(node, type, pattern) === true)) return null;
2351
- if (include) {
2352
- const applicable = include.map(({ type, pattern }) => matchesSchemaPattern(node, type, pattern)).filter((r) => r !== null);
2353
- if (applicable.length > 0 && !applicable.includes(true)) return null;
2354
- }
2355
- const overrideOptions = override.find(({ type, pattern }) => matchesSchemaPattern(node, type, pattern) === true)?.options;
2356
- return {
2357
- ...options,
2358
- ...overrideOptions
2359
- };
2360
- }
2361
- return options;
2362
- }
2363
- //#endregion
2364
- export { AsyncEventEmitter, FunctionParams, PackageManager, PluginDriver, URLPath, build, build as default, defineAdapter, defineConfig, defineGenerator, defineLogger, definePlugin, definePrinter, defineStorage, detectFormatter, detectLinter, formatters, fsStorage, getBarrelFiles, getConfigs, getMode, isInputPath, linters, logLevel, memoryStorage, resolveOptions, safeBuild, setup };
1340
+ export { AsyncEventEmitter, FileManager, FileProcessor, PluginDriver, URLPath, ast, createAdapter, createKubb, createRenderer, createStorage, defineGenerator, defineLogger, defineMiddleware, defineParser, definePlugin, defineResolver, fsStorage, isInputPath, logLevel, memoryStorage };
2365
1341
 
2366
1342
  //# sourceMappingURL=index.js.map