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

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