@kubb/core 5.0.0-alpha.6 → 5.0.0-alpha.61

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