@kubb/core 5.0.0-alpha.5 → 5.0.0-alpha.50

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