@kubb/core 5.0.0-alpha.9 → 5.0.0-beta.2

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