@kubb/core 5.0.0-beta.67 → 5.0.0-beta.69

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.
@@ -25,6 +25,8 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
25
25
  }) : target, mod));
26
26
  //#endregion
27
27
  let node_events = require("node:events");
28
+ let node_fs_promises = require("node:fs/promises");
29
+ let node_path = require("node:path");
28
30
  let _kubb_ast = require("@kubb/ast");
29
31
  let _kubb_ast_utils = require("@kubb/ast/utils");
30
32
  //#region ../../internals/utils/src/errors.ts
@@ -239,6 +241,180 @@ function pascalCase(text, { prefix = "", suffix = "" } = {}) {
239
241
  return toCamelOrPascal(`${prefix} ${text} ${suffix}`, true);
240
242
  }
241
243
  //#endregion
244
+ //#region ../../internals/utils/src/runtime.ts
245
+ /**
246
+ * Detects the JavaScript runtime executing the current process and exposes its name and version.
247
+ *
248
+ * Prefer the shared {@link runtime} instance over constructing your own.
249
+ */
250
+ var Runtime = class {
251
+ /**
252
+ * `true` when the current process is running under Bun.
253
+ *
254
+ * Detection keys off the global `Bun` object rather than `process.versions`,
255
+ * because Bun polyfills `process.versions.node` for Node compatibility and would
256
+ * otherwise look like Node.
257
+ *
258
+ * @example
259
+ * ```ts
260
+ * if (runtime.isBun) {
261
+ * await Bun.write(path, data)
262
+ * }
263
+ * ```
264
+ */
265
+ get isBun() {
266
+ return typeof Bun !== "undefined";
267
+ }
268
+ /**
269
+ * `true` when the current process is running under Deno.
270
+ */
271
+ get isDeno() {
272
+ return typeof globalThis.Deno !== "undefined";
273
+ }
274
+ /**
275
+ * `true` when the current process is running under Node.
276
+ *
277
+ * Bun and Deno are excluded first so a polyfilled `process` does not register as Node.
278
+ */
279
+ get isNode() {
280
+ return !this.isBun && !this.isDeno && typeof process !== "undefined" && process.versions?.node != null;
281
+ }
282
+ /**
283
+ * Name of the runtime executing the current process.
284
+ *
285
+ * @example
286
+ * ```ts
287
+ * runtime.name // 'bun' when run with `bun kubb`, 'node' otherwise
288
+ * ```
289
+ */
290
+ get name() {
291
+ if (this.isBun) return "bun";
292
+ if (this.isDeno) return "deno";
293
+ return "node";
294
+ }
295
+ /**
296
+ * Version of the active runtime, or an empty string when it cannot be read.
297
+ *
298
+ * @example
299
+ * ```ts
300
+ * runtime.version // '1.3.11' under Bun, '22.22.2' under Node
301
+ * ```
302
+ */
303
+ get version() {
304
+ if (this.isBun) return process.versions.bun ?? "";
305
+ if (this.isDeno) return globalThis.Deno?.version?.deno ?? "";
306
+ return process.versions?.node ?? "";
307
+ }
308
+ };
309
+ /**
310
+ * Shared {@link Runtime} instance describing the JavaScript runtime executing the current process.
311
+ */
312
+ const runtime = new Runtime();
313
+ //#endregion
314
+ //#region ../../internals/utils/src/fs.ts
315
+ /**
316
+ * Reads the file at `path` as a UTF-8 string.
317
+ * Uses `Bun.file().text()` when running under Bun, `fs.readFile` otherwise.
318
+ *
319
+ * @example
320
+ * ```ts
321
+ * const source = await read('./src/Pet.ts')
322
+ * ```
323
+ */
324
+ async function read(path) {
325
+ if (runtime.isBun) return Bun.file(path).text();
326
+ return (0, node_fs_promises.readFile)(path, { encoding: "utf8" });
327
+ }
328
+ /**
329
+ * Writes `data` to `path`, trimming leading/trailing whitespace before saving.
330
+ * Skips the write when the trimmed content is empty or identical to what is already on disk.
331
+ * Creates any missing parent directories automatically.
332
+ * When `sanity` is `true`, re-reads the file after writing and throws if the content does not match.
333
+ *
334
+ * @example
335
+ * ```ts
336
+ * await write('./src/Pet.ts', source) // writes and returns trimmed content
337
+ * await write('./src/Pet.ts', source) // null — file unchanged
338
+ * await write('./src/Pet.ts', ' ') // null — empty content skipped
339
+ * ```
340
+ */
341
+ async function write(path, data, options = {}) {
342
+ const trimmed = data.trim();
343
+ if (trimmed === "") return null;
344
+ const resolved = (0, node_path.resolve)(path);
345
+ if (runtime.isBun) {
346
+ const file = Bun.file(resolved);
347
+ if ((await file.exists() ? await file.text() : null) === trimmed) return null;
348
+ await Bun.write(resolved, trimmed);
349
+ return trimmed;
350
+ }
351
+ try {
352
+ if (await (0, node_fs_promises.readFile)(resolved, { encoding: "utf-8" }) === trimmed) return null;
353
+ } catch {}
354
+ await (0, node_fs_promises.mkdir)((0, node_path.dirname)(resolved), { recursive: true });
355
+ await (0, node_fs_promises.writeFile)(resolved, trimmed, { encoding: "utf-8" });
356
+ if (options.sanity) {
357
+ const savedData = await (0, node_fs_promises.readFile)(resolved, { encoding: "utf-8" });
358
+ if (savedData !== trimmed) throw new Error(`Sanity check failed for ${path}\n\nData[${data.length}]:\n${data}\n\nSaved[${savedData.length}]:\n${savedData}\n`);
359
+ return savedData;
360
+ }
361
+ return trimmed;
362
+ }
363
+ /**
364
+ * Recursively removes `path`. Silently succeeds when `path` does not exist.
365
+ *
366
+ * @example
367
+ * ```ts
368
+ * await clean('./dist')
369
+ * ```
370
+ */
371
+ async function clean(path) {
372
+ return (0, node_fs_promises.rm)(path, {
373
+ recursive: true,
374
+ force: true
375
+ });
376
+ }
377
+ /**
378
+ * Converts a filesystem path to use POSIX (`/`) separators.
379
+ *
380
+ * Most of the codebase compares and composes paths as strings (prefix matching, joining for
381
+ * import specifiers, splitting on `/`). On POSIX `path.resolve` already returns `/`-separated
382
+ * paths, but on Windows it returns `\`-separated paths, which breaks every such comparison.
383
+ *
384
+ * Routing every path that crosses a module boundary through `toPosixPath` keeps the rest of the
385
+ * code platform-agnostic. The conversion runs unconditionally so Windows-specific behavior is
386
+ * exercisable from POSIX CI.
387
+ *
388
+ * @example
389
+ * toPosixPath('C:\\repo\\src\\pet.ts') // 'C:/repo/src/pet.ts'
390
+ */
391
+ function toPosixPath(filePath) {
392
+ return filePath.replaceAll("\\", "/");
393
+ }
394
+ /**
395
+ * Builds a nested file path from a dotted name. Splits on dots that precede a letter
396
+ * (so version numbers embedded in operationIds like `v2025.0` stay intact), camelCases
397
+ * every earlier segment, applies `caseLast` to the final segment, and joins with `/`.
398
+ *
399
+ * Empty segments are dropped before joining. They arise when the name starts with a dot
400
+ * followed by a letter (e.g. `..Schema` splits into `['..', 'Schema']` and `'..'` cases to
401
+ * an empty string). Without this a leading `/` would form, which `path.resolve` reads as an
402
+ * absolute path, letting generated files escape the configured output directory.
403
+ *
404
+ * @example Nested path from a dotted name
405
+ * `toFilePath('pet.petId') // 'pet/petId'`
406
+ *
407
+ * @example PascalCase the final segment
408
+ * `toFilePath('pet.Pet', pascalCase) // 'pet/Pet'`
409
+ *
410
+ * @example Suffix applied to the final segment only
411
+ * `toFilePath('tag.tag', (part) => camelCase(part, { suffix: 'schema' })) // 'tag/tagSchema'`
412
+ */
413
+ function toFilePath(name, caseLast = camelCase) {
414
+ const parts = name.split(/\.(?=[a-zA-Z])/);
415
+ return parts.map((part, i) => i === parts.length - 1 ? caseLast(part) : camelCase(part)).filter(Boolean).join("/");
416
+ }
417
+ //#endregion
242
418
  //#region src/constants.ts
243
419
  /**
244
420
  * Plugin `include` filter types that select operations directly. When one of these is set
@@ -389,6 +565,7 @@ function mergeFile(a, b) {
389
565
  ...a,
390
566
  banner: b.banner,
391
567
  footer: b.footer,
568
+ copy: b.copy ?? a.copy,
392
569
  sources: a.sources.length ? b.sources.length ? [...a.sources, ...b.sources] : a.sources : b.sources,
393
570
  imports: a.imports.length ? b.imports.length ? [...a.imports, ...b.imports] : a.imports : b.imports,
394
571
  exports: a.exports.length ? b.exports.length ? [...a.exports, ...b.exports] : a.exports : b.exports
@@ -495,6 +672,19 @@ function joinSources(file) {
495
672
  }
496
673
  return parts.join("\n\n");
497
674
  }
675
+ async function parseCopy(file) {
676
+ let content;
677
+ try {
678
+ content = await read(file.copy);
679
+ } catch (err) {
680
+ throw new Error(`[kubb] Could not copy file into output: ${file.copy}`, { cause: err });
681
+ }
682
+ return [
683
+ file.banner,
684
+ content,
685
+ file.footer
686
+ ].filter((segment) => Boolean(segment)).map((segment) => segment.trimEnd()).join("\n");
687
+ }
498
688
  /**
499
689
  * Turns `FileNode`s into source strings and writes them to storage.
500
690
  *
@@ -527,7 +717,8 @@ var FileProcessor = class {
527
717
  get size() {
528
718
  return this.#pending.size;
529
719
  }
530
- parse(file) {
720
+ async parse(file) {
721
+ if (file.copy) return parseCopy(file);
531
722
  const parsers = this.#parsers;
532
723
  const parseExtName = this.#extension?.[file.extname] || void 0;
533
724
  if (!parsers || !file.extname) return joinSources(file);
@@ -535,12 +726,12 @@ var FileProcessor = class {
535
726
  if (!parser) return joinSources(file);
536
727
  return parser.parse(file, { extname: parseExtName });
537
728
  }
538
- *stream(files) {
729
+ async *stream(files) {
539
730
  const total = files.length;
540
731
  if (total === 0) return;
541
732
  let processed = 0;
542
733
  for (const file of files) {
543
- const source = this.parse(file);
734
+ const source = await this.parse(file);
544
735
  processed++;
545
736
  yield {
546
737
  file,
@@ -553,7 +744,7 @@ var FileProcessor = class {
553
744
  }
554
745
  async run(files) {
555
746
  await this.hooks.emit("start", files);
556
- for (const { file, source, processed, total, percentage } of this.stream(files)) await this.hooks.emit("update", {
747
+ for await (const { file, source, processed, total, percentage } of this.stream(files)) await this.hooks.emit("update", {
557
748
  file,
558
749
  source,
559
750
  processed,
@@ -602,7 +793,7 @@ var FileProcessor = class {
602
793
  const storage = this.#storage;
603
794
  await this.hooks.emit("start", files);
604
795
  const queue = [];
605
- for (const item of this.stream(files)) {
796
+ for await (const item of this.stream(files)) {
606
797
  await this.hooks.emit("update", item);
607
798
  if (item.source) {
608
799
  queue.push(storage.setItem(item.file.path, item.source));
@@ -788,6 +979,12 @@ Object.defineProperty(exports, "camelCase", {
788
979
  return camelCase;
789
980
  }
790
981
  });
982
+ Object.defineProperty(exports, "clean", {
983
+ enumerable: true,
984
+ get: function() {
985
+ return clean;
986
+ }
987
+ });
791
988
  Object.defineProperty(exports, "createStorage", {
792
989
  enumerable: true,
793
990
  get: function() {
@@ -818,5 +1015,29 @@ Object.defineProperty(exports, "pascalCase", {
818
1015
  return pascalCase;
819
1016
  }
820
1017
  });
1018
+ Object.defineProperty(exports, "runtime", {
1019
+ enumerable: true,
1020
+ get: function() {
1021
+ return runtime;
1022
+ }
1023
+ });
1024
+ Object.defineProperty(exports, "toFilePath", {
1025
+ enumerable: true,
1026
+ get: function() {
1027
+ return toFilePath;
1028
+ }
1029
+ });
1030
+ Object.defineProperty(exports, "toPosixPath", {
1031
+ enumerable: true,
1032
+ get: function() {
1033
+ return toPosixPath;
1034
+ }
1035
+ });
1036
+ Object.defineProperty(exports, "write", {
1037
+ enumerable: true,
1038
+ get: function() {
1039
+ return write;
1040
+ }
1041
+ });
821
1042
 
822
- //# sourceMappingURL=memoryStorage-67VG6jZn.cjs.map
1043
+ //# sourceMappingURL=memoryStorage-CfycFGzX.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"memoryStorage-CfycFGzX.cjs","names":["#emitter","NodeEventEmitter","#emitAll","#cache","#store","#dedupe","ast","#sorted","#parsers","#storage","#extension","#pending","#runningFlush","#processAndWrite"],"sources":["../../../internals/utils/src/errors.ts","../../../internals/utils/src/asyncEventEmitter.ts","../../../internals/utils/src/casing.ts","../../../internals/utils/src/runtime.ts","../../../internals/utils/src/fs.ts","../src/constants.ts","../src/createStorage.ts","../src/FileManager.ts","../src/FileProcessor.ts","../src/storages/memoryStorage.ts"],"sourcesContent":["/**\n * Thrown when one or more errors occur during a Kubb build.\n * Carries the full list of underlying errors on `errors`.\n *\n * @example\n * ```ts\n * throw new BuildError('Build failed', { errors: [err1, err2] })\n * ```\n */\nexport class BuildError extends Error {\n errors: Array<Error>\n\n constructor(message: string, options: { cause?: Error; errors: Array<Error> }) {\n super(message, { cause: options.cause })\n this.name = 'BuildError'\n this.errors = options.errors\n }\n}\n\n/**\n * Coerces an unknown thrown value to an `Error` instance.\n * Returns the value as-is when it is already an `Error`; otherwise wraps it with `String(value)`.\n *\n * @example\n * ```ts\n * try { ... } catch(err) {\n * throw new BuildError('Build failed', { cause: toError(err), errors: [] })\n * }\n * ```\n */\nexport function toError(value: unknown): Error {\n return value instanceof Error ? value : new Error(String(value))\n}\n\n/**\n * Extracts a human-readable message from any thrown value.\n *\n * @example\n * ```ts\n * getErrorMessage(new Error('oops')) // 'oops'\n * getErrorMessage('plain string') // 'plain string'\n * ```\n */\nexport function getErrorMessage(value: unknown): string {\n return value instanceof Error ? value.message : String(value)\n}\n\n/**\n * Extracts the `.cause` of an `Error` as an `Error`, or `undefined` when absent or not an `Error`.\n *\n * @example\n * ```ts\n * const cause = toCause(buildError) // Error | undefined\n * ```\n */\nexport function toCause(error: Error): Error | undefined {\n return error.cause instanceof Error ? error.cause : undefined\n}\n","import { EventEmitter as NodeEventEmitter } from 'node:events'\nimport { toError } from './errors.ts'\n\n/**\n * A function that can be registered as an event listener, synchronous or async.\n */\ntype AsyncListener<TArgs extends Array<unknown>> = (...args: TArgs) => void | Promise<void>\n\n/**\n * Typed `EventEmitter` that awaits all async listeners before resolving.\n * Wraps Node's `EventEmitter` with full TypeScript event-map inference.\n *\n * @example\n * ```ts\n * const emitter = new AsyncEventEmitter<{ build: [name: string] }>()\n * emitter.on('build', async (name) => { console.log(name) })\n * await emitter.emit('build', 'petstore') // all listeners awaited\n * ```\n */\nexport class AsyncEventEmitter<TEvents extends { [K in keyof TEvents]: Array<unknown> }> {\n /**\n * Maximum number of listeners per event before Node emits a memory-leak warning.\n * @default 10\n */\n constructor(maxListener = 10) {\n this.#emitter.setMaxListeners(maxListener)\n }\n\n #emitter = new NodeEventEmitter()\n\n /**\n * Emits `eventName` and awaits all registered listeners sequentially.\n * Throws if any listener rejects, wrapping the cause with the event name and serialized arguments.\n *\n * @example\n * ```ts\n * await emitter.emit('build', 'petstore')\n * ```\n */\n emit<TEventName extends keyof TEvents & string>(eventName: TEventName, ...eventArgs: TEvents[TEventName]): Promise<void> | void {\n const listeners = this.#emitter.listeners(eventName) as Array<AsyncListener<TEvents[TEventName]>>\n\n if (listeners.length === 0) {\n return\n }\n\n return this.#emitAll(eventName, listeners, eventArgs)\n }\n\n async #emitAll<TEventName extends keyof TEvents & string>(\n eventName: TEventName,\n listeners: Array<AsyncListener<TEvents[TEventName]>>,\n eventArgs: TEvents[TEventName],\n ): Promise<void> {\n for (const listener of listeners) {\n try {\n await listener(...eventArgs)\n } catch (err) {\n let serializedArgs: string\n try {\n serializedArgs = JSON.stringify(eventArgs)\n } catch {\n serializedArgs = String(eventArgs)\n }\n throw new Error(`Error in async listener for \"${eventName}\" with eventArgs ${serializedArgs}`, { cause: toError(err) })\n }\n }\n }\n\n /**\n * Registers a persistent listener for `eventName`.\n *\n * @example\n * ```ts\n * emitter.on('build', async (name) => { console.log(name) })\n * ```\n */\n on<TEventName extends keyof TEvents & string>(eventName: TEventName, handler: AsyncListener<TEvents[TEventName]>): void {\n this.#emitter.on(eventName, handler as AsyncListener<Array<unknown>>)\n }\n\n /**\n * Registers a one-shot listener that removes itself after the first invocation.\n *\n * @example\n * ```ts\n * emitter.onOnce('build', async (name) => { console.log(name) })\n * ```\n */\n onOnce<TEventName extends keyof TEvents & string>(eventName: TEventName, handler: AsyncListener<TEvents[TEventName]>): void {\n const wrapper: AsyncListener<TEvents[TEventName]> = (...args) => {\n this.off(eventName, wrapper)\n return handler(...args)\n }\n this.on(eventName, wrapper)\n }\n\n /**\n * Removes a previously registered listener.\n *\n * @example\n * ```ts\n * emitter.off('build', handler)\n * ```\n */\n off<TEventName extends keyof TEvents & string>(eventName: TEventName, handler: AsyncListener<TEvents[TEventName]>): void {\n this.#emitter.off(eventName, handler as AsyncListener<Array<unknown>>)\n }\n\n /**\n * Returns the number of listeners registered for `eventName`.\n *\n * @example\n * ```ts\n * emitter.on('build', handler)\n * emitter.listenerCount('build') // 1\n * ```\n */\n listenerCount<TEventName extends keyof TEvents & string>(eventName: TEventName): number {\n return this.#emitter.listenerCount(eventName)\n }\n\n /**\n * Raises or lowers the per-event listener ceiling before Node warns about a memory leak.\n * Set this above the expected listener count when many listeners attach by design.\n *\n * @example\n * ```ts\n * emitter.setMaxListeners(40)\n * ```\n */\n setMaxListeners(max: number): void {\n this.#emitter.setMaxListeners(max)\n }\n\n /**\n * Returns the current per-event listener ceiling.\n */\n getMaxListeners(): number {\n return this.#emitter.getMaxListeners()\n }\n\n /**\n * Removes all listeners from every event channel.\n *\n * @example\n * ```ts\n * emitter.removeAll()\n * ```\n */\n removeAll(): void {\n this.#emitter.removeAllListeners()\n }\n}\n","type Options = {\n /**\n * Text prepended before casing is applied.\n */\n prefix?: string\n /**\n * Text appended before casing is applied.\n */\n suffix?: string\n}\n\n/**\n * Shared implementation for camelCase and PascalCase conversion.\n * Splits on common word boundaries (spaces, hyphens, underscores, dots, slashes, colons)\n * and capitalizes each word according to `pascal`.\n *\n * When `pascal` is `true` the first word is also capitalized (PascalCase), otherwise only subsequent words are.\n */\nfunction toCamelOrPascal(text: string, pascal: boolean): string {\n return text\n .trim()\n .replace(/([a-z\\d])([A-Z])/g, '$1 $2')\n .replace(/([A-Z]+)([A-Z][a-z])/g, '$1 $2')\n .replace(/(\\d)([a-z])/g, '$1 $2')\n .split(/[\\s\\-_./\\\\:]+/)\n .filter(Boolean)\n .map((word, i) => {\n if (word.length > 1 && word === word.toUpperCase()) return word\n const head = i === 0 && !pascal ? word.charAt(0).toLowerCase() : word.charAt(0).toUpperCase()\n return head + word.slice(1)\n })\n .join('')\n .replace(/[^a-zA-Z0-9]/g, '')\n}\n\n/**\n * Converts `text` to camelCase.\n *\n * @example Word boundaries\n * `camelCase('hello-world') // 'helloWorld'`\n *\n * @example With a prefix\n * `camelCase('tag', { prefix: 'create' }) // 'createTag'`\n */\nexport function camelCase(text: string, { prefix = '', suffix = '' }: Options = {}): string {\n return toCamelOrPascal(`${prefix} ${text} ${suffix}`, false)\n}\n\n/**\n * Converts `text` to PascalCase.\n *\n * @example Word boundaries\n * `pascalCase('hello-world') // 'HelloWorld'`\n *\n * @example With a suffix\n * `pascalCase('tag', { suffix: 'schema' }) // 'TagSchema'`\n */\nexport function pascalCase(text: string, { prefix = '', suffix = '' }: Options = {}): string {\n return toCamelOrPascal(`${prefix} ${text} ${suffix}`, true)\n}\n","/**\n * Name of the JavaScript runtime executing the current process.\n */\ntype RuntimeName = 'bun' | 'deno' | 'node'\n\n/**\n * Detects the JavaScript runtime executing the current process and exposes its name and version.\n *\n * Prefer the shared {@link runtime} instance over constructing your own.\n */\nclass Runtime {\n /**\n * `true` when the current process is running under Bun.\n *\n * Detection keys off the global `Bun` object rather than `process.versions`,\n * because Bun polyfills `process.versions.node` for Node compatibility and would\n * otherwise look like Node.\n *\n * @example\n * ```ts\n * if (runtime.isBun) {\n * await Bun.write(path, data)\n * }\n * ```\n */\n get isBun(): boolean {\n return typeof Bun !== 'undefined'\n }\n\n /**\n * `true` when the current process is running under Deno.\n */\n get isDeno(): boolean {\n return typeof (globalThis as { Deno?: unknown }).Deno !== 'undefined'\n }\n\n /**\n * `true` when the current process is running under Node.\n *\n * Bun and Deno are excluded first so a polyfilled `process` does not register as Node.\n */\n get isNode(): boolean {\n return !this.isBun && !this.isDeno && typeof process !== 'undefined' && process.versions?.node != null\n }\n\n /**\n * Name of the runtime executing the current process.\n *\n * @example\n * ```ts\n * runtime.name // 'bun' when run with `bun kubb`, 'node' otherwise\n * ```\n */\n get name(): RuntimeName {\n if (this.isBun) return 'bun'\n if (this.isDeno) return 'deno'\n\n return 'node'\n }\n\n /**\n * Version of the active runtime, or an empty string when it cannot be read.\n *\n * @example\n * ```ts\n * runtime.version // '1.3.11' under Bun, '22.22.2' under Node\n * ```\n */\n get version(): string {\n if (this.isBun) return process.versions.bun ?? ''\n if (this.isDeno) return (globalThis as { Deno?: { version?: { deno?: string } } }).Deno?.version?.deno ?? ''\n\n return process.versions?.node ?? ''\n }\n}\n\n/**\n * Shared {@link Runtime} instance describing the JavaScript runtime executing the current process.\n */\nexport const runtime = new Runtime()\n","import { existsSync } from 'node:fs'\nimport { access, mkdir, readFile, rm, writeFile } from 'node:fs/promises'\nimport { dirname, join, posix, resolve } from 'node:path'\nimport { camelCase } from './casing.ts'\nimport { runtime } from './runtime.ts'\n\n/**\n * Walks up the directory tree from `cwd` (defaults to `process.cwd()`) and\n * returns the absolute path of the nearest `package.json`, or `null` when none\n * is found before reaching the filesystem root.\n *\n * @example\n * ```ts\n * const pkgPath = findPackageJSON('/home/user/project/src') // '/home/user/project/package.json'\n * ```\n */\nexport function findPackageJSON(cwd?: string): string | null {\n let dir = cwd ? resolve(cwd) : process.cwd()\n while (true) {\n const pkgPath = join(dir, 'package.json')\n if (existsSync(pkgPath)) return pkgPath\n const parent = dirname(dir)\n if (parent === dir) return null\n dir = parent\n }\n}\n\n/**\n * Converts all backslashes to forward slashes.\n * Extended-length Windows paths (`\\\\?\\...`) are left unchanged.\n */\nfunction toSlash(p: string): string {\n if (p.startsWith('\\\\\\\\?\\\\')) return p\n\n return p.replaceAll('\\\\', '/')\n}\n\n/**\n * Returns the relative path from `rootDir` to `filePath`, always using forward slashes\n * and prefixed with `./` when not already traversing upward.\n *\n * @example\n * ```ts\n * getRelativePath('/src/components', '/src/components/Button.tsx') // './Button.tsx'\n * getRelativePath('/src/components', '/src/utils/helpers.ts') // '../utils/helpers.ts'\n * ```\n */\nexport function getRelativePath(rootDir?: string | null, filePath?: string | null): string {\n if (!rootDir || !filePath) {\n throw new Error(`Root and file should be filled in when retrieving the relativePath, ${rootDir || ''} ${filePath || ''}`)\n }\n\n const relativePath = posix.relative(toSlash(rootDir), toSlash(filePath))\n\n return relativePath.startsWith('../') ? relativePath : `./${relativePath}`\n}\n\n/**\n * Resolves to `true` when the file or directory at `path` exists.\n * Uses `Bun.file().exists()` when running under Bun, `fs.access` otherwise.\n *\n * @example\n * ```ts\n * if (await exists('./kubb.config.ts')) {\n * const content = await read('./kubb.config.ts')\n * }\n * ```\n */\nexport async function exists(path: string): Promise<boolean> {\n if (runtime.isBun) {\n return Bun.file(path).exists()\n }\n return access(path).then(\n () => true,\n () => false,\n )\n}\n\n/**\n * Reads the file at `path` as a UTF-8 string.\n * Uses `Bun.file().text()` when running under Bun, `fs.readFile` otherwise.\n *\n * @example\n * ```ts\n * const source = await read('./src/Pet.ts')\n * ```\n */\nexport async function read(path: string): Promise<string> {\n if (runtime.isBun) {\n return Bun.file(path).text()\n }\n return readFile(path, { encoding: 'utf8' })\n}\n\ntype WriteOptions = {\n /**\n * When `true`, re-reads the file immediately after writing and throws if the\n * content does not match — useful for catching write failures on unreliable file systems.\n */\n sanity?: boolean\n}\n\n/**\n * Writes `data` to `path`, trimming leading/trailing whitespace before saving.\n * Skips the write when the trimmed content is empty or identical to what is already on disk.\n * Creates any missing parent directories automatically.\n * When `sanity` is `true`, re-reads the file after writing and throws if the content does not match.\n *\n * @example\n * ```ts\n * await write('./src/Pet.ts', source) // writes and returns trimmed content\n * await write('./src/Pet.ts', source) // null — file unchanged\n * await write('./src/Pet.ts', ' ') // null — empty content skipped\n * ```\n */\nexport async function write(path: string, data: string, options: WriteOptions = {}): Promise<string | null> {\n const trimmed = data.trim()\n if (trimmed === '') return null\n\n const resolved = resolve(path)\n\n if (runtime.isBun) {\n const file = Bun.file(resolved)\n const oldContent = (await file.exists()) ? await file.text() : null\n if (oldContent === trimmed) return null\n await Bun.write(resolved, trimmed)\n return trimmed\n }\n\n try {\n const oldContent = await readFile(resolved, { encoding: 'utf-8' })\n if (oldContent === trimmed) return null\n } catch {\n /* file doesn't exist yet */\n }\n\n await mkdir(dirname(resolved), { recursive: true })\n await writeFile(resolved, trimmed, { encoding: 'utf-8' })\n\n if (options.sanity) {\n const savedData = await readFile(resolved, { encoding: 'utf-8' })\n if (savedData !== trimmed) {\n throw new Error(`Sanity check failed for ${path}\\n\\nData[${data.length}]:\\n${data}\\n\\nSaved[${savedData.length}]:\\n${savedData}\\n`)\n }\n return savedData\n }\n\n return trimmed\n}\n\n/**\n * Recursively removes `path`. Silently succeeds when `path` does not exist.\n *\n * @example\n * ```ts\n * await clean('./dist')\n * ```\n */\nexport async function clean(path: string): Promise<void> {\n return rm(path, { recursive: true, force: true })\n}\n\n/**\n * Converts a filesystem path to use POSIX (`/`) separators.\n *\n * Most of the codebase compares and composes paths as strings (prefix matching, joining for\n * import specifiers, splitting on `/`). On POSIX `path.resolve` already returns `/`-separated\n * paths, but on Windows it returns `\\`-separated paths, which breaks every such comparison.\n *\n * Routing every path that crosses a module boundary through `toPosixPath` keeps the rest of the\n * code platform-agnostic. The conversion runs unconditionally so Windows-specific behavior is\n * exercisable from POSIX CI.\n *\n * @example\n * toPosixPath('C:\\\\repo\\\\src\\\\pet.ts') // 'C:/repo/src/pet.ts'\n */\nexport function toPosixPath(filePath: string): string {\n return filePath.replaceAll('\\\\', '/')\n}\n\n/**\n * Strips the file extension from a path or file name.\n * Only removes the last `.ext` segment when the dot is not part of a directory name.\n *\n * @example\n * trimExtName('petStore.ts') // 'petStore'\n * trimExtName('/src/models/pet.ts') // '/src/models/pet'\n * trimExtName('/project.v2/gen/pet.ts') // '/project.v2/gen/pet'\n * trimExtName('noExtension') // 'noExtension'\n */\nexport function trimExtName(text: string): string {\n const dotIndex = text.lastIndexOf('.')\n if (dotIndex > 0 && !text.includes('/', dotIndex)) {\n return text.slice(0, dotIndex)\n }\n return text\n}\n\n/**\n * Builds a nested file path from a dotted name. Splits on dots that precede a letter\n * (so version numbers embedded in operationIds like `v2025.0` stay intact), camelCases\n * every earlier segment, applies `caseLast` to the final segment, and joins with `/`.\n *\n * Empty segments are dropped before joining. They arise when the name starts with a dot\n * followed by a letter (e.g. `..Schema` splits into `['..', 'Schema']` and `'..'` cases to\n * an empty string). Without this a leading `/` would form, which `path.resolve` reads as an\n * absolute path, letting generated files escape the configured output directory.\n *\n * @example Nested path from a dotted name\n * `toFilePath('pet.petId') // 'pet/petId'`\n *\n * @example PascalCase the final segment\n * `toFilePath('pet.Pet', pascalCase) // 'pet/Pet'`\n *\n * @example Suffix applied to the final segment only\n * `toFilePath('tag.tag', (part) => camelCase(part, { suffix: 'schema' })) // 'tag/tagSchema'`\n */\nexport function toFilePath(name: string, caseLast: (part: string) => string = camelCase): string {\n const parts = name.split(/\\.(?=[a-zA-Z])/)\n return parts\n .map((part, i) => (i === parts.length - 1 ? caseLast(part) : camelCase(part)))\n .filter(Boolean)\n .join('/')\n}\n","/**\n * Number of file writes to batch in parallel during `flushPendingFiles`.\n */\nexport const STREAM_FLUSH_EVERY = 50\n\n/**\n * Maximum number of █ characters in a plugin timing bar.\n */\nexport const SUMMARY_MAX_BAR_LENGTH = 10 as const\n\n/**\n * Divides elapsed milliseconds into bar-length units (1 block per 100 ms).\n */\nexport const SUMMARY_TIME_SCALE_DIVISOR = 100 as const\n\n/**\n * Number of schema/operation nodes to dispatch concurrently during generation.\n */\nexport const SCHEMA_PARALLEL = 8\n\n/**\n * Upper bound of hook listeners a single plugin can add to one event (its schema, operation,\n * and operations generators, plus lifecycle hooks). Used to size the hooks emitter's\n * max-listener ceiling so a multi-generator plugin set does not trip Node's leak warning.\n */\nexport const HOOK_LISTENERS_PER_PLUGIN = 4\n\n/**\n * Plugin `include` filter types that select operations directly. When one of these is set\n * without a `schemaName` include, the generate phase pre-scans operations to compute the set\n * of schemas they reach, so unreachable schemas can be pruned for that plugin.\n */\nexport const OPERATION_FILTER_TYPES: ReadonlySet<string> = new Set(['tag', 'operationId', 'path', 'method', 'contentType'])\n\n/**\n * Stable codes Kubb attaches to a `Diagnostic`. Each maps to a known failure mode\n * and stays stable so it can be referenced in tooling and (later) docs. Reference\n * these instead of inlining the string at a throw site.\n */\nexport const diagnosticCode = {\n /**\n * Fallback for an unstructured error with no specific code.\n */\n unknown: 'KUBB_UNKNOWN',\n /**\n * The `input.path` file or URL could not be read.\n */\n inputNotFound: 'KUBB_INPUT_NOT_FOUND',\n /**\n * An adapter was configured without an `input`.\n */\n inputRequired: 'KUBB_INPUT_REQUIRED',\n /**\n * A `$ref` (or equivalent reference) could not be resolved in the source document.\n */\n refNotFound: 'KUBB_REF_NOT_FOUND',\n /**\n * A server variable value is not allowed by its `enum`.\n */\n invalidServerVariable: 'KUBB_INVALID_SERVER_VARIABLE',\n /**\n * A required plugin is missing from the config.\n */\n pluginNotFound: 'KUBB_PLUGIN_NOT_FOUND',\n /**\n * A plugin threw while generating.\n */\n pluginFailed: 'KUBB_PLUGIN_FAILED',\n /**\n * A plugin reported a non-fatal warning through `ctx.warn`.\n */\n pluginWarning: 'KUBB_PLUGIN_WARNING',\n /**\n * A plugin reported an informational message through `ctx.info`.\n */\n pluginInfo: 'KUBB_PLUGIN_INFO',\n /**\n * A schema uses a `format` Kubb does not map to a specific type. Reserved for\n * adapters to emit as a `warning`.\n */\n unsupportedFormat: 'KUBB_UNSUPPORTED_FORMAT',\n /**\n * A referenced schema or operation is marked `deprecated`. Reserved for adapters\n * to emit as an `info`.\n */\n deprecated: 'KUBB_DEPRECATED',\n /**\n * An adapter is required but the config has none. The build cannot read the input\n * without one.\n */\n adapterRequired: 'KUBB_ADAPTER_REQUIRED',\n /**\n * A resolved output path escapes the output directory, which can stem from a path\n * traversal in the spec or a misconfigured `group.name`.\n */\n pathTraversal: 'KUBB_PATH_TRAVERSAL',\n /**\n * A plugin's options are invalid, for example `output.mode: 'file'` paired with a `group` option.\n */\n invalidPluginOptions: 'KUBB_INVALID_PLUGIN_OPTIONS',\n /**\n * A post-generate shell hook (`hooks.done`) exited with a failure.\n */\n hookFailed: 'KUBB_HOOK_FAILED',\n /**\n * The formatter pass over the generated files failed.\n */\n formatFailed: 'KUBB_FORMAT_FAILED',\n /**\n * The linter pass over the generated files failed.\n */\n lintFailed: 'KUBB_LINT_FAILED',\n /**\n * Not a failure. Carries a plugin's elapsed time, summed into the run total.\n */\n performance: 'KUBB_PERFORMANCE',\n /**\n * Not a failure. A newer Kubb version is available on npm.\n */\n updateAvailable: 'KUBB_UPDATE_AVAILABLE',\n} as const\n\n/**\n * Union of the stable {@link diagnosticCode} values.\n */\nexport type DiagnosticCode = (typeof diagnosticCode)[keyof typeof diagnosticCode]\n","/**\n * Backend that persists generated files. Kubb ships with `fsStorage` (writes\n * to disk) and `memoryStorage` (keeps everything in RAM). Implement this\n * interface to write somewhere else, such as S3 or a database.\n */\nexport type Storage = {\n /**\n * Identifier used in logs and diagnostics (`'fs'`, `'memory'`, `'s3'`).\n */\n readonly name: string\n /**\n * Returns `true` when an entry for `key` exists.\n */\n hasItem(key: string): Promise<boolean>\n /**\n * Reads the stored string. Returns `null` when the key is missing.\n */\n getItem(key: string): Promise<string | null>\n /**\n * Stores `value` under `key`, creating any required structure (directories,\n * buckets, ...).\n */\n setItem(key: string, value: string): Promise<void>\n /**\n * Deletes the entry for `key`. No-op when the key does not exist.\n */\n removeItem(key: string): Promise<void>\n /**\n * Returns every key. Pass `base` to filter to keys starting with that prefix.\n */\n getKeys(base?: string): Promise<Array<string>>\n /**\n * Removes every entry. Pass `base` to scope the wipe to a key prefix.\n */\n clear(base?: string): Promise<void>\n /**\n * Optional teardown hook for a backend to flush buffers, close connections,\n * or release file locks.\n */\n dispose?(): Promise<void>\n}\n\n/**\n * Defines a custom storage backend. The builder receives user options and\n * returns a `Storage` implementation. Kubb ships with filesystem and in-memory\n * storages. A custom backend writes generated files elsewhere, such as cloud\n * storage or a database.\n *\n * @example In-memory storage (the built-in implementation)\n * ```ts\n * import { createStorage } from '@kubb/core'\n *\n * export const memoryStorage = createStorage(() => {\n * const store = new Map<string, string>()\n *\n * return {\n * name: 'memory',\n * async hasItem(key) {\n * return store.has(key)\n * },\n * async getItem(key) {\n * return store.get(key) ?? null\n * },\n * async setItem(key, value) {\n * store.set(key, value)\n * },\n * async removeItem(key) {\n * store.delete(key)\n * },\n * async getKeys(base) {\n * const keys = [...store.keys()]\n * return base ? keys.filter((k) => k.startsWith(base)) : keys\n * },\n * async clear(base) {\n * if (!base) store.clear()\n * },\n * }\n * })\n * ```\n */\nexport function createStorage<TOptions = Record<string, never>>(build: (options: TOptions) => Storage): (options?: TOptions) => Storage {\n return (options) => build(options ?? ({} as TOptions))\n}\n","import { AsyncEventEmitter } from '@internals/utils'\nimport { ast, type FileNode } from '@kubb/ast'\n\n/**\n * Hooks fired by a `FileManager`.\n *\n * - `upsert` fires once per resolved file added through `add` or `upsert`.\n */\nexport type FileManagerHooks = {\n upsert: [file: FileNode]\n}\n\nfunction mergeFile<TMeta extends object = object>(a: FileNode<TMeta>, b: FileNode<TMeta>): FileNode<TMeta> {\n return {\n ...a,\n // Incoming file (b) takes precedence for banner/footer so a barrel file (whose\n // banner/footer the barrel plugin resolves last) wins over a plugin-generated\n // file at the same path.\n banner: b.banner,\n footer: b.footer,\n // A verbatim-copy file cannot be merged with rendered content; the incoming `copy` wins.\n copy: b.copy ?? a.copy,\n sources: a.sources.length ? (b.sources.length ? [...a.sources, ...b.sources] : a.sources) : b.sources,\n imports: a.imports.length ? (b.imports.length ? [...a.imports, ...b.imports] : a.imports) : b.imports,\n exports: a.exports.length ? (b.exports.length ? [...a.exports, ...b.exports] : a.exports) : b.exports,\n }\n}\n\nfunction isIndexPath(path: string): boolean {\n return path.endsWith('/index.ts') || path === 'index.ts'\n}\n\n// Sort order: shortest path first. Within a length bucket, index.ts barrels last.\nfunction compareFiles(a: FileNode, b: FileNode): number {\n const lenDiff = a.path.length - b.path.length\n if (lenDiff !== 0) return lenDiff\n const aIsIndex = isIndexPath(a.path)\n const bIsIndex = isIndexPath(b.path)\n if (aIsIndex && !bIsIndex) return 1\n if (!aIsIndex && bIsIndex) return -1\n return 0\n}\n\n/**\n * In-memory file store for generated files. Files sharing a `path` are merged\n * (sources/imports/exports concatenated). The `files` getter is sorted by\n * path length (barrel `index.ts` last within a bucket).\n *\n * @example\n * ```ts\n * const manager = new FileManager()\n * manager.upsert(myFile)\n * manager.files // sorted view\n * ```\n */\nexport class FileManager {\n /**\n * Subscribe to file-store changes. Listeners on `upsert` see each resolved file as it lands\n * through `add` or `upsert`.\n */\n readonly hooks = new AsyncEventEmitter<FileManagerHooks>()\n readonly #cache = new Map<string, FileNode>()\n // Cached sorted view. Null means stale and rebuilt lazily on next `files` read.\n // Nulled (not mutated) on every write so callers holding a prior reference keep\n // their snapshot. `dispose()` must not silently empty an array the consumer\n // already holds.\n #sorted: Array<FileNode> | null = null\n\n add(...files: Array<FileNode>): Array<FileNode> {\n return this.#store(files, false)\n }\n\n upsert(...files: Array<FileNode>): Array<FileNode> {\n return this.#store(files, true)\n }\n\n #store(files: ReadonlyArray<FileNode>, mergeExisting: boolean): Array<FileNode> {\n const batch = files.length > 1 ? this.#dedupe(files) : files\n const resolved: Array<FileNode> = []\n\n for (const file of batch) {\n const existing = this.#cache.get(file.path)\n const merged = existing && mergeExisting ? ast.factory.createFile(mergeFile(existing, file)) : ast.factory.createFile(file)\n this.#cache.set(merged.path, merged)\n resolved.push(merged)\n this.hooks.emit('upsert', merged)\n }\n\n if (resolved.length > 0) this.#sorted = null\n return resolved\n }\n\n // Merges same-path entries within a batch so the cache update loop stays\n // uniform. Only called for multi-file batches.\n #dedupe(files: ReadonlyArray<FileNode>): Array<FileNode> {\n const seen = new Map<string, FileNode>()\n for (const file of files) {\n const prev = seen.get(file.path)\n seen.set(file.path, prev ? mergeFile(prev, file) : file)\n }\n return [...seen.values()]\n }\n\n getByPath(path: string): FileNode | null {\n return this.#cache.get(path) ?? null\n }\n\n deleteByPath(path: string): void {\n if (!this.#cache.delete(path)) return\n this.#sorted = null\n }\n\n clear(): void {\n this.#cache.clear()\n this.#sorted = null\n }\n\n /**\n * Releases all stored files and clears every `hooks` listener. Called by the core after\n * `kubb:build:end`.\n */\n dispose(): void {\n this.clear()\n this.hooks.removeAll()\n }\n\n [Symbol.dispose](): void {\n this.dispose()\n }\n\n /**\n * All stored files in stable sort order (shortest path first, barrel files\n * last within a length bucket). Returns a cached view, do not mutate.\n */\n get files(): Array<FileNode> {\n return (this.#sorted ??= [...this.#cache.values()].sort(compareFiles))\n }\n}\n","import { AsyncEventEmitter, read } from '@internals/utils'\nimport type { CodeNode, FileNode } from '@kubb/ast'\nimport { extractStringsFromNodes } from '@kubb/ast/utils'\nimport { STREAM_FLUSH_EVERY } from './constants.ts'\nimport type { Storage } from './createStorage.ts'\nimport type { Parser } from './defineParser.ts'\n\n/**\n * Hooks fired by a `FileProcessor`.\n *\n * - `start` opens a batch, from `run` or a queue flush.\n * - `update` fires once per file as it is converted.\n * - `end` closes a batch.\n * - `enqueue` fires for every `enqueue` call.\n * - `drain` fires when `drain()` empties the queue with no in-flight batch left.\n */\nexport type FileProcessorHooks = {\n start: [files: Array<FileNode>]\n update: [params: { file: FileNode; source?: string; processed: number; total: number; percentage: number }]\n end: [files: Array<FileNode>]\n enqueue: [file: FileNode]\n drain: []\n}\n\n/**\n * Per-file progress record yielded by `stream` and surfaced through the `update` event.\n */\nexport type ParsedFile = {\n file: FileNode\n source: string\n processed: number\n total: number\n percentage: number\n}\n\ntype FileProcessorOptions = {\n /**\n * Storage destination for queued writes.\n */\n storage: Storage\n /**\n * Parsers indexed by file extension.\n */\n parsers?: Map<FileNode['extname'], Parser>\n /**\n * Output extname per source extname, applied during conversion.\n */\n extension?: Record<FileNode['extname'], FileNode['extname'] | ''>\n}\n\nfunction joinSources(file: FileNode): string {\n const sources = file.sources\n if (sources.length === 0) return ''\n const parts: Array<string> = []\n for (const source of sources) {\n const text = extractStringsFromNodes(source.nodes as Array<CodeNode>)\n if (text) parts.push(text)\n }\n return parts.join('\\n\\n')\n}\n\nasync function parseCopy(file: FileNode): Promise<string> {\n let content: string\n try {\n content = await read(file.copy as string)\n } catch (err) {\n throw new Error(`[kubb] Could not copy file into output: ${file.copy}`, { cause: err })\n }\n\n return [file.banner, content, file.footer]\n .filter((segment): segment is string => Boolean(segment))\n .map((segment) => segment.trimEnd())\n .join('\\n')\n}\n\n/**\n * Turns `FileNode`s into source strings and writes them to storage.\n *\n * Two modes share the same instance. Stateless mode (`parse`, `stream`, `run`) just runs the\n * conversion. Queue mode (`enqueue`, `flush`, `drain`) buffers files deduped by path and\n * writes each batch through storage with up to `STREAM_FLUSH_EVERY` requests in flight.\n *\n * `flush` does not wait for its batch to finish, so dispatch can overlap with IO. The next\n * `flush` or `drain` picks the in-flight batch up. `drain` blocks until everything has been\n * written and is meant for the end of a build.\n *\n * To surface build-level hook signals (`kubb:files:processing:*` and friends) subscribe to\n * `hooks` and re-emit on the kubb bus.\n */\nexport class FileProcessor {\n readonly hooks = new AsyncEventEmitter<FileProcessorHooks>()\n readonly #parsers: Map<FileNode['extname'], Parser> | null\n readonly #storage: Storage\n readonly #extension: Record<FileNode['extname'], FileNode['extname'] | ''> | null\n readonly #pending = new Map<string, FileNode>()\n #runningFlush: Promise<void> | null = null\n\n constructor(options: FileProcessorOptions) {\n this.#parsers = options.parsers ?? null\n this.#storage = options.storage\n this.#extension = options.extension ?? null\n }\n\n /**\n * Files waiting in the queue.\n */\n get size(): number {\n return this.#pending.size\n }\n\n async parse(file: FileNode): Promise<string> {\n if (file.copy) {\n return parseCopy(file)\n }\n\n const parsers = this.#parsers\n const parseExtName = this.#extension?.[file.extname] || undefined\n\n if (!parsers || !file.extname) {\n return joinSources(file)\n }\n\n const parser = parsers.get(file.extname)\n\n if (!parser) {\n return joinSources(file)\n }\n\n return parser.parse(file, { extname: parseExtName })\n }\n\n async *stream(files: ReadonlyArray<FileNode>): AsyncGenerator<ParsedFile> {\n const total = files.length\n if (total === 0) return\n\n let processed = 0\n for (const file of files) {\n const source = await this.parse(file)\n processed++\n\n yield { file, source, processed, total, percentage: (processed / total) * 100 }\n }\n }\n\n async run(files: Array<FileNode>): Promise<Array<FileNode>> {\n await this.hooks.emit('start', files)\n\n for await (const { file, source, processed, total, percentage } of this.stream(files)) {\n await this.hooks.emit('update', { file, source, processed, percentage, total })\n }\n\n await this.hooks.emit('end', files)\n\n return files\n }\n\n /**\n * Adds a file to the next flush. A later `enqueue` for the same path replaces the previous\n * entry, matching `FileManager.upsert`. Fires the `enqueue` event.\n */\n enqueue(file: FileNode): void {\n this.#pending.set(file.path, file)\n this.hooks.emit('enqueue', file)\n }\n\n /**\n * Starts processing the queued files. Waits for any previous flush to finish (so two\n * batches never run together) and then returns without waiting for the new one. The next\n * `flush` or `drain` picks up the in-flight task.\n */\n async flush(): Promise<void> {\n if (this.#runningFlush) await this.#runningFlush\n if (this.#pending.size === 0) return\n\n const batch = [...this.#pending.values()]\n this.#pending.clear()\n\n this.#runningFlush = this.#processAndWrite(batch).finally(() => {\n this.#runningFlush = null\n })\n }\n\n /**\n * Waits for the in-flight flush and writes any files still queued. Fires the `drain` event\n * when both are done.\n */\n async drain(): Promise<void> {\n if (this.#runningFlush) await this.#runningFlush\n\n if (this.#pending.size > 0) {\n const batch = [...this.#pending.values()]\n this.#pending.clear()\n await this.#processAndWrite(batch)\n }\n\n await this.hooks.emit('drain')\n }\n\n async #processAndWrite(files: Array<FileNode>): Promise<void> {\n const storage = this.#storage\n\n await this.hooks.emit('start', files)\n\n // Single pass: each file's write starts right after its `update` fires, so IO overlaps\n // parsing and the batch never holds every rendered source in memory at once.\n const queue: Array<Promise<void>> = []\n for await (const item of this.stream(files)) {\n await this.hooks.emit('update', item)\n if (item.source) {\n queue.push(storage.setItem(item.file.path, item.source))\n if (queue.length >= STREAM_FLUSH_EVERY) await Promise.all(queue.splice(0))\n }\n }\n await Promise.all(queue)\n\n await this.hooks.emit('end', files)\n }\n\n /**\n * Clears every listener and the pending queue.\n */\n dispose(): void {\n this.hooks.removeAll()\n this.#pending.clear()\n }\n\n [Symbol.dispose](): void {\n this.dispose()\n }\n}\n","import { createStorage } from '../createStorage.ts'\n\n/**\n * In-memory storage driver. Useful for testing and dry-run scenarios where\n * generated output should be captured without touching the filesystem.\n *\n * All data lives in a `Map` scoped to the storage instance and is discarded\n * when the instance is garbage-collected.\n *\n * @example\n * ```ts\n * import { memoryStorage } from '@kubb/core'\n * import { defineConfig } from 'kubb'\n *\n * export default defineConfig({\n * input: { path: './petStore.yaml' },\n * output: { path: './src/gen' },\n * storage: memoryStorage(),\n * })\n * ```\n */\nexport const memoryStorage = createStorage(() => {\n const store = new Map<string, string>()\n\n return {\n name: 'memory',\n async hasItem(key: string) {\n return store.has(key)\n },\n async getItem(key: string) {\n return store.get(key) ?? null\n },\n async setItem(key: string, value: string) {\n store.set(key, value)\n },\n async removeItem(key: string) {\n store.delete(key)\n },\n async getKeys(base?: string) {\n const keys = [...store.keys()]\n return base ? keys.filter((k) => k.startsWith(base)) : keys\n },\n async clear(base?: string) {\n if (!base) {\n store.clear()\n return\n }\n for (const key of store.keys()) {\n if (key.startsWith(base)) {\n store.delete(key)\n }\n }\n },\n }\n})\n"],"mappings":"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"}
package/dist/mocks.cjs CHANGED
@@ -1,5 +1,5 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- const require_memoryStorage = require("./memoryStorage-67VG6jZn.cjs");
2
+ const require_memoryStorage = require("./memoryStorage-CfycFGzX.cjs");
3
3
  let node_path = require("node:path");
4
4
  node_path = require_memoryStorage.__toESM(node_path, 1);
5
5
  let _kubb_ast = require("@kubb/ast");
@@ -185,7 +185,7 @@ async function matchFiles(files, options = {}) {
185
185
  const processed = /* @__PURE__ */ new Map();
186
186
  for (const file of files) {
187
187
  if (!file?.path || processed.has(file.path)) continue;
188
- const parsed = fileProcessor.parse(file);
188
+ const parsed = await fileProcessor.parse(file);
189
189
  const code = file.baseName.endsWith(".json") || !format ? parsed : await format(parsed);
190
190
  processed.set(file.path, code);
191
191
  const snapshotPath = node_path.default.join("__snapshots__", ...pre ? [require_memoryStorage.camelCase(pre)] : [], file.baseName);
@@ -1 +1 @@
1
- {"version":3,"file":"mocks.cjs","names":["FileManager","FileProcessor","memoryStorage","path","camelCase"],"sources":["../src/mocks.ts"],"sourcesContent":["import path, { resolve } from 'node:path'\nimport { camelCase } from '@internals/utils'\nimport type { FileNode, InputMeta, Macro, OperationNode, SchemaNode } from '@kubb/ast'\nimport { applyMacros } from '@kubb/ast'\nimport { expect } from 'vitest'\nimport type { Parser } from './defineParser.ts'\nimport { FileManager } from './FileManager.ts'\nimport { FileProcessor } from './FileProcessor.ts'\nimport type { KubbDriver } from './KubbDriver.ts'\nimport { memoryStorage } from './storages/memoryStorage.ts'\nimport type { Adapter, AdapterFactoryOptions, Config, Generator, GeneratorContext, NormalizedPlugin, PluginFactoryOptions, RendererFactory } from './types.ts'\n\n/**\n * Creates a minimal `KubbDriver` mock for unit tests.\n */\nexport function createMockedPluginDriver(options: { name?: string; plugin?: NormalizedPlugin; config?: Config } = {}): KubbDriver {\n const fileManager = new FileManager()\n\n return {\n config: options?.config ?? {\n root: '.',\n output: {\n path: './path',\n },\n },\n getPlugin(_pluginName: string): NormalizedPlugin | undefined {\n return options?.plugin\n },\n getResolver: (_pluginName: string) => options?.plugin?.resolver,\n fileManager,\n async dispatch({ result, renderer }: { result: unknown; renderer?: RendererFactory | null }): Promise<void> {\n if (!result) return\n\n if (Array.isArray(result)) {\n fileManager.upsert(...(result as Array<FileNode>))\n return\n }\n\n if (!renderer) return\n\n using instance = renderer()\n if (instance.stream) {\n for (const file of instance.stream(result)) fileManager.upsert(file)\n return\n }\n\n await instance.render(result)\n fileManager.upsert(...instance.files)\n },\n } as unknown as KubbDriver\n}\n\n/**\n * Creates a minimal `Adapter` mock for unit tests.\n * `parse` returns an empty `InputNode` by default. Override via `options.parse`.\n * `getImports` returns `[]` by default.\n */\nexport function createMockedAdapter<TOptions extends AdapterFactoryOptions = AdapterFactoryOptions>(\n options: {\n name?: TOptions['name']\n resolvedOptions?: TOptions['resolvedOptions']\n parse?: Adapter<TOptions>['parse']\n getImports?: Adapter<TOptions>['getImports']\n } = {},\n): Adapter<TOptions> {\n return {\n name: (options.name ?? 'oas') as TOptions['name'],\n options: (options.resolvedOptions ?? {}) as TOptions['resolvedOptions'],\n parse: options.parse ?? (async () => ({ kind: 'Input' as const, schemas: [], operations: [] })),\n getImports: options.getImports ?? ((_node: SchemaNode, _resolve: (schemaName: string) => { name: string; path: string }) => []),\n } as Adapter<TOptions>\n}\n\n/**\n * Creates a minimal plugin mock for unit tests.\n *\n * @example\n * `const plugin = createMockedPlugin<PluginTs>({ name: '@kubb/plugin-ts', options })`\n */\nexport function createMockedPlugin<TOptions extends PluginFactoryOptions = PluginFactoryOptions>(params: {\n name: TOptions['name']\n options: TOptions['resolvedOptions']\n resolver?: TOptions['resolver']\n macros?: Array<Macro>\n dependencies?: Array<string>\n}): NormalizedPlugin<TOptions> {\n return {\n name: params.name,\n options: params.options,\n resolver: params.resolver,\n macros: params.macros,\n dependencies: params.dependencies,\n hooks: {},\n } as unknown as NormalizedPlugin<TOptions>\n}\n\ntype RenderGeneratorOptions<TOptions extends PluginFactoryOptions> = {\n config: Config\n adapter: Adapter\n meta?: InputMeta\n driver: KubbDriver\n plugin: NormalizedPlugin<TOptions>\n options: TOptions['resolvedOptions']\n resolver: TOptions['resolver']\n}\n\nfunction createMockedPluginContext<TOptions extends PluginFactoryOptions>(opts: RenderGeneratorOptions<TOptions>): Omit<GeneratorContext<TOptions>, 'options'> {\n const root = resolve(opts.config.root, opts.config.output.path)\n\n return {\n config: opts.config,\n root,\n adapter: opts.adapter,\n resolver: opts.resolver,\n plugin: opts.plugin,\n driver: opts.driver,\n getResolver: (name: string) => opts.driver.getResolver(name),\n meta: opts.meta ?? { circularNames: [], enumNames: [] },\n addFile: async (...files: Array<FileNode>) => opts.driver.fileManager.add(...files),\n upsertFile: async (...files: Array<FileNode>) => opts.driver.fileManager.upsert(...files),\n hooks: opts.driver.hooks ?? ({} as never),\n warn: (msg: string) => console.warn(msg),\n error: (msg: string) => console.error(msg),\n info: (msg: string) => console.info(msg),\n } as unknown as Omit<GeneratorContext<TOptions>, 'options'>\n}\n\n/**\n * Renders a generator's `schema` method in a test context.\n *\n * @example\n * ```ts\n * await renderGeneratorSchema(typeGenerator, node, { config, adapter, driver, plugin, options, resolver })\n * await matchFiles(driver.fileManager.files)\n * ```\n */\nexport async function renderGeneratorSchema<TOptions extends PluginFactoryOptions>(\n generator: Generator<TOptions>,\n node: SchemaNode,\n opts: RenderGeneratorOptions<TOptions>,\n): Promise<void> {\n if (!generator.schema) return\n const context = createMockedPluginContext(opts)\n const transformedNode = opts.plugin.macros?.length ? applyMacros(node, opts.plugin.macros) : node\n const result = await generator.schema(transformedNode, {\n ...context,\n options: opts.options,\n })\n await opts.driver.dispatch({ result, renderer: generator.renderer })\n}\n\n/**\n * Renders a generator's `operation` method in a test context.\n *\n * @example\n * ```ts\n * await renderGeneratorOperation(typeGenerator, node, { config, adapter, driver, plugin, options, resolver })\n * await matchFiles(driver.fileManager.files)\n * ```\n */\nexport async function renderGeneratorOperation<TOptions extends PluginFactoryOptions>(\n generator: Generator<TOptions>,\n node: OperationNode,\n opts: RenderGeneratorOptions<TOptions>,\n): Promise<void> {\n if (!generator.operation) return\n const context = createMockedPluginContext(opts)\n const transformedNode = opts.plugin.macros?.length ? applyMacros(node, opts.plugin.macros) : node\n const result = await generator.operation(transformedNode, {\n ...context,\n options: opts.options,\n })\n await opts.driver.dispatch({ result, renderer: generator.renderer })\n}\n\n/**\n * Renders a generator's `operations` method in a test context.\n *\n * @example\n * ```ts\n * await renderGeneratorOperations(classClientGenerator, nodes, { config, adapter, driver, plugin, options, resolver })\n * await matchFiles(driver.fileManager.files)\n * ```\n */\nexport async function renderGeneratorOperations<TOptions extends PluginFactoryOptions>(\n generator: Generator<TOptions>,\n nodes: Array<OperationNode>,\n opts: RenderGeneratorOptions<TOptions>,\n): Promise<void> {\n if (!generator.operations) return\n const context = createMockedPluginContext(opts)\n const transformedNodes = opts.plugin.macros?.length ? nodes.map((n) => applyMacros(n, opts.plugin.macros!)) : nodes\n const result = await generator.operations(transformedNodes, {\n ...context,\n options: opts.options,\n })\n await opts.driver.dispatch({ result, renderer: generator.renderer })\n}\n\ntype MatchFilesOptions = {\n /**\n * Parsers indexed by file extension, used to render each `FileNode` to source.\n * Without a matching parser the file's raw content is used.\n */\n parsers?: Map<FileNode['extname'], Parser>\n /**\n * Formatter applied to non-JSON output before snapshotting, e.g. prettier. When\n * omitted the parsed source is snapshotted as-is.\n */\n format?: (source?: string) => string | Promise<string>\n /**\n * Subfolder under `__snapshots__`, camelCased. Useful to keep variant snapshots apart.\n */\n pre?: string\n}\n\n/**\n * Renders the driver's collected `FileNode`s to source and asserts each against a file snapshot.\n * Pair it with the `renderGenerator*` helpers to snapshot a generator's output.\n *\n * @example\n * ```ts\n * await renderGeneratorSchema(typeGenerator, node, { config, adapter, driver, plugin, options, resolver })\n * await matchFiles(driver.fileManager.files, { parsers, format })\n * ```\n */\nexport async function matchFiles(files: Array<FileNode> | undefined, options: MatchFilesOptions = {}): Promise<Map<string, string> | undefined> {\n if (!files?.length) return\n\n const { parsers = new Map(), format, pre } = options\n const fileProcessor = new FileProcessor({ storage: memoryStorage(), parsers })\n const processed = new Map<string, string>()\n\n for (const file of files) {\n if (!file?.path || processed.has(file.path)) {\n continue\n }\n\n const parsed = fileProcessor.parse(file)\n const code = file.baseName.endsWith('.json') || !format ? parsed : await format(parsed)\n\n processed.set(file.path, code)\n\n const snapshotPath = path.join('__snapshots__', ...(pre ? [camelCase(pre)] : []), file.baseName)\n await expect(code).toMatchFileSnapshot(snapshotPath)\n }\n\n return processed\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"mocks.cjs","names":["FileManager","FileProcessor","memoryStorage","path","camelCase"],"sources":["../src/mocks.ts"],"sourcesContent":["import path, { resolve } from 'node:path'\nimport { camelCase } from '@internals/utils'\nimport type { FileNode, InputMeta, Macro, OperationNode, SchemaNode } from '@kubb/ast'\nimport { applyMacros } from '@kubb/ast'\nimport { expect } from 'vitest'\nimport type { Parser } from './defineParser.ts'\nimport { FileManager } from './FileManager.ts'\nimport { FileProcessor } from './FileProcessor.ts'\nimport type { KubbDriver } from './KubbDriver.ts'\nimport { memoryStorage } from './storages/memoryStorage.ts'\nimport type { Adapter, AdapterFactoryOptions, Config, Generator, GeneratorContext, NormalizedPlugin, PluginFactoryOptions, RendererFactory } from './types.ts'\n\n/**\n * Creates a minimal `KubbDriver` mock for unit tests.\n */\nexport function createMockedPluginDriver(options: { name?: string; plugin?: NormalizedPlugin; config?: Config } = {}): KubbDriver {\n const fileManager = new FileManager()\n\n return {\n config: options?.config ?? {\n root: '.',\n output: {\n path: './path',\n },\n },\n getPlugin(_pluginName: string): NormalizedPlugin | undefined {\n return options?.plugin\n },\n getResolver: (_pluginName: string) => options?.plugin?.resolver,\n fileManager,\n async dispatch({ result, renderer }: { result: unknown; renderer?: RendererFactory | null }): Promise<void> {\n if (!result) return\n\n if (Array.isArray(result)) {\n fileManager.upsert(...(result as Array<FileNode>))\n return\n }\n\n if (!renderer) return\n\n using instance = renderer()\n if (instance.stream) {\n for (const file of instance.stream(result)) fileManager.upsert(file)\n return\n }\n\n await instance.render(result)\n fileManager.upsert(...instance.files)\n },\n } as unknown as KubbDriver\n}\n\n/**\n * Creates a minimal `Adapter` mock for unit tests.\n * `parse` returns an empty `InputNode` by default. Override via `options.parse`.\n * `getImports` returns `[]` by default.\n */\nexport function createMockedAdapter<TOptions extends AdapterFactoryOptions = AdapterFactoryOptions>(\n options: {\n name?: TOptions['name']\n resolvedOptions?: TOptions['resolvedOptions']\n parse?: Adapter<TOptions>['parse']\n getImports?: Adapter<TOptions>['getImports']\n } = {},\n): Adapter<TOptions> {\n return {\n name: (options.name ?? 'oas') as TOptions['name'],\n options: (options.resolvedOptions ?? {}) as TOptions['resolvedOptions'],\n parse: options.parse ?? (async () => ({ kind: 'Input' as const, schemas: [], operations: [] })),\n getImports: options.getImports ?? ((_node: SchemaNode, _resolve: (schemaName: string) => { name: string; path: string }) => []),\n } as Adapter<TOptions>\n}\n\n/**\n * Creates a minimal plugin mock for unit tests.\n *\n * @example\n * `const plugin = createMockedPlugin<PluginTs>({ name: '@kubb/plugin-ts', options })`\n */\nexport function createMockedPlugin<TOptions extends PluginFactoryOptions = PluginFactoryOptions>(params: {\n name: TOptions['name']\n options: TOptions['resolvedOptions']\n resolver?: TOptions['resolver']\n macros?: Array<Macro>\n dependencies?: Array<string>\n}): NormalizedPlugin<TOptions> {\n return {\n name: params.name,\n options: params.options,\n resolver: params.resolver,\n macros: params.macros,\n dependencies: params.dependencies,\n hooks: {},\n } as unknown as NormalizedPlugin<TOptions>\n}\n\ntype RenderGeneratorOptions<TOptions extends PluginFactoryOptions> = {\n config: Config\n adapter: Adapter\n meta?: InputMeta\n driver: KubbDriver\n plugin: NormalizedPlugin<TOptions>\n options: TOptions['resolvedOptions']\n resolver: TOptions['resolver']\n}\n\nfunction createMockedPluginContext<TOptions extends PluginFactoryOptions>(opts: RenderGeneratorOptions<TOptions>): Omit<GeneratorContext<TOptions>, 'options'> {\n const root = resolve(opts.config.root, opts.config.output.path)\n\n return {\n config: opts.config,\n root,\n adapter: opts.adapter,\n resolver: opts.resolver,\n plugin: opts.plugin,\n driver: opts.driver,\n getResolver: (name: string) => opts.driver.getResolver(name),\n meta: opts.meta ?? { circularNames: [], enumNames: [] },\n addFile: async (...files: Array<FileNode>) => opts.driver.fileManager.add(...files),\n upsertFile: async (...files: Array<FileNode>) => opts.driver.fileManager.upsert(...files),\n hooks: opts.driver.hooks ?? ({} as never),\n warn: (msg: string) => console.warn(msg),\n error: (msg: string) => console.error(msg),\n info: (msg: string) => console.info(msg),\n } as unknown as Omit<GeneratorContext<TOptions>, 'options'>\n}\n\n/**\n * Renders a generator's `schema` method in a test context.\n *\n * @example\n * ```ts\n * await renderGeneratorSchema(typeGenerator, node, { config, adapter, driver, plugin, options, resolver })\n * await matchFiles(driver.fileManager.files)\n * ```\n */\nexport async function renderGeneratorSchema<TOptions extends PluginFactoryOptions>(\n generator: Generator<TOptions>,\n node: SchemaNode,\n opts: RenderGeneratorOptions<TOptions>,\n): Promise<void> {\n if (!generator.schema) return\n const context = createMockedPluginContext(opts)\n const transformedNode = opts.plugin.macros?.length ? applyMacros(node, opts.plugin.macros) : node\n const result = await generator.schema(transformedNode, {\n ...context,\n options: opts.options,\n })\n await opts.driver.dispatch({ result, renderer: generator.renderer })\n}\n\n/**\n * Renders a generator's `operation` method in a test context.\n *\n * @example\n * ```ts\n * await renderGeneratorOperation(typeGenerator, node, { config, adapter, driver, plugin, options, resolver })\n * await matchFiles(driver.fileManager.files)\n * ```\n */\nexport async function renderGeneratorOperation<TOptions extends PluginFactoryOptions>(\n generator: Generator<TOptions>,\n node: OperationNode,\n opts: RenderGeneratorOptions<TOptions>,\n): Promise<void> {\n if (!generator.operation) return\n const context = createMockedPluginContext(opts)\n const transformedNode = opts.plugin.macros?.length ? applyMacros(node, opts.plugin.macros) : node\n const result = await generator.operation(transformedNode, {\n ...context,\n options: opts.options,\n })\n await opts.driver.dispatch({ result, renderer: generator.renderer })\n}\n\n/**\n * Renders a generator's `operations` method in a test context.\n *\n * @example\n * ```ts\n * await renderGeneratorOperations(classClientGenerator, nodes, { config, adapter, driver, plugin, options, resolver })\n * await matchFiles(driver.fileManager.files)\n * ```\n */\nexport async function renderGeneratorOperations<TOptions extends PluginFactoryOptions>(\n generator: Generator<TOptions>,\n nodes: Array<OperationNode>,\n opts: RenderGeneratorOptions<TOptions>,\n): Promise<void> {\n if (!generator.operations) return\n const context = createMockedPluginContext(opts)\n const transformedNodes = opts.plugin.macros?.length ? nodes.map((n) => applyMacros(n, opts.plugin.macros!)) : nodes\n const result = await generator.operations(transformedNodes, {\n ...context,\n options: opts.options,\n })\n await opts.driver.dispatch({ result, renderer: generator.renderer })\n}\n\ntype MatchFilesOptions = {\n /**\n * Parsers indexed by file extension, used to render each `FileNode` to source.\n * Without a matching parser the file's raw content is used.\n */\n parsers?: Map<FileNode['extname'], Parser>\n /**\n * Formatter applied to non-JSON output before snapshotting, e.g. prettier. When\n * omitted the parsed source is snapshotted as-is.\n */\n format?: (source?: string) => string | Promise<string>\n /**\n * Subfolder under `__snapshots__`, camelCased. Useful to keep variant snapshots apart.\n */\n pre?: string\n}\n\n/**\n * Renders the driver's collected `FileNode`s to source and asserts each against a file snapshot.\n * Pair it with the `renderGenerator*` helpers to snapshot a generator's output.\n *\n * @example\n * ```ts\n * await renderGeneratorSchema(typeGenerator, node, { config, adapter, driver, plugin, options, resolver })\n * await matchFiles(driver.fileManager.files, { parsers, format })\n * ```\n */\nexport async function matchFiles(files: Array<FileNode> | undefined, options: MatchFilesOptions = {}): Promise<Map<string, string> | undefined> {\n if (!files?.length) return\n\n const { parsers = new Map(), format, pre } = options\n const fileProcessor = new FileProcessor({ storage: memoryStorage(), parsers })\n const processed = new Map<string, string>()\n\n for (const file of files) {\n if (!file?.path || processed.has(file.path)) {\n continue\n }\n\n const parsed = await fileProcessor.parse(file)\n const code = file.baseName.endsWith('.json') || !format ? parsed : await format(parsed)\n\n processed.set(file.path, code)\n\n const snapshotPath = path.join('__snapshots__', ...(pre ? [camelCase(pre)] : []), file.baseName)\n await expect(code).toMatchFileSnapshot(snapshotPath)\n }\n\n return processed\n}\n"],"mappings":"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"}
package/dist/mocks.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { t as __name } from "./rolldown-runtime-C0LytTxp.js";
2
- import { At as Adapter, G as Generator, J as KubbDriver, U as Parser, h as Config, jt as AdapterFactoryOptions, st as PluginFactoryOptions, tt as NormalizedPlugin } from "./diagnostics-M8ckppMO.js";
2
+ import { At as Adapter, G as Generator, J as KubbDriver, U as Parser, h as Config, jt as AdapterFactoryOptions, st as PluginFactoryOptions, tt as NormalizedPlugin } from "./diagnostics-CJtO1uSM.js";
3
3
  import { FileNode, InputMeta, Macro, OperationNode, SchemaNode } from "@kubb/ast";
4
4
 
5
5
  //#region src/mocks.d.ts
package/dist/mocks.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import "./rolldown-runtime-C0LytTxp.js";
2
- import { c as camelCase, i as FileManager, n as _usingCtx, r as FileProcessor, t as memoryStorage } from "./memoryStorage-D0gb5tRV.js";
2
+ import { i as FileManager, n as _usingCtx, p as camelCase, r as FileProcessor, t as memoryStorage } from "./memoryStorage-B4VTTIpQ.js";
3
3
  import path, { resolve } from "node:path";
4
4
  import { applyMacros } from "@kubb/ast";
5
5
  import { expect } from "vitest";
@@ -184,7 +184,7 @@ async function matchFiles(files, options = {}) {
184
184
  const processed = /* @__PURE__ */ new Map();
185
185
  for (const file of files) {
186
186
  if (!file?.path || processed.has(file.path)) continue;
187
- const parsed = fileProcessor.parse(file);
187
+ const parsed = await fileProcessor.parse(file);
188
188
  const code = file.baseName.endsWith(".json") || !format ? parsed : await format(parsed);
189
189
  processed.set(file.path, code);
190
190
  const snapshotPath = path.join("__snapshots__", ...pre ? [camelCase(pre)] : [], file.baseName);
package/dist/mocks.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"mocks.js","names":[],"sources":["../src/mocks.ts"],"sourcesContent":["import path, { resolve } from 'node:path'\nimport { camelCase } from '@internals/utils'\nimport type { FileNode, InputMeta, Macro, OperationNode, SchemaNode } from '@kubb/ast'\nimport { applyMacros } from '@kubb/ast'\nimport { expect } from 'vitest'\nimport type { Parser } from './defineParser.ts'\nimport { FileManager } from './FileManager.ts'\nimport { FileProcessor } from './FileProcessor.ts'\nimport type { KubbDriver } from './KubbDriver.ts'\nimport { memoryStorage } from './storages/memoryStorage.ts'\nimport type { Adapter, AdapterFactoryOptions, Config, Generator, GeneratorContext, NormalizedPlugin, PluginFactoryOptions, RendererFactory } from './types.ts'\n\n/**\n * Creates a minimal `KubbDriver` mock for unit tests.\n */\nexport function createMockedPluginDriver(options: { name?: string; plugin?: NormalizedPlugin; config?: Config } = {}): KubbDriver {\n const fileManager = new FileManager()\n\n return {\n config: options?.config ?? {\n root: '.',\n output: {\n path: './path',\n },\n },\n getPlugin(_pluginName: string): NormalizedPlugin | undefined {\n return options?.plugin\n },\n getResolver: (_pluginName: string) => options?.plugin?.resolver,\n fileManager,\n async dispatch({ result, renderer }: { result: unknown; renderer?: RendererFactory | null }): Promise<void> {\n if (!result) return\n\n if (Array.isArray(result)) {\n fileManager.upsert(...(result as Array<FileNode>))\n return\n }\n\n if (!renderer) return\n\n using instance = renderer()\n if (instance.stream) {\n for (const file of instance.stream(result)) fileManager.upsert(file)\n return\n }\n\n await instance.render(result)\n fileManager.upsert(...instance.files)\n },\n } as unknown as KubbDriver\n}\n\n/**\n * Creates a minimal `Adapter` mock for unit tests.\n * `parse` returns an empty `InputNode` by default. Override via `options.parse`.\n * `getImports` returns `[]` by default.\n */\nexport function createMockedAdapter<TOptions extends AdapterFactoryOptions = AdapterFactoryOptions>(\n options: {\n name?: TOptions['name']\n resolvedOptions?: TOptions['resolvedOptions']\n parse?: Adapter<TOptions>['parse']\n getImports?: Adapter<TOptions>['getImports']\n } = {},\n): Adapter<TOptions> {\n return {\n name: (options.name ?? 'oas') as TOptions['name'],\n options: (options.resolvedOptions ?? {}) as TOptions['resolvedOptions'],\n parse: options.parse ?? (async () => ({ kind: 'Input' as const, schemas: [], operations: [] })),\n getImports: options.getImports ?? ((_node: SchemaNode, _resolve: (schemaName: string) => { name: string; path: string }) => []),\n } as Adapter<TOptions>\n}\n\n/**\n * Creates a minimal plugin mock for unit tests.\n *\n * @example\n * `const plugin = createMockedPlugin<PluginTs>({ name: '@kubb/plugin-ts', options })`\n */\nexport function createMockedPlugin<TOptions extends PluginFactoryOptions = PluginFactoryOptions>(params: {\n name: TOptions['name']\n options: TOptions['resolvedOptions']\n resolver?: TOptions['resolver']\n macros?: Array<Macro>\n dependencies?: Array<string>\n}): NormalizedPlugin<TOptions> {\n return {\n name: params.name,\n options: params.options,\n resolver: params.resolver,\n macros: params.macros,\n dependencies: params.dependencies,\n hooks: {},\n } as unknown as NormalizedPlugin<TOptions>\n}\n\ntype RenderGeneratorOptions<TOptions extends PluginFactoryOptions> = {\n config: Config\n adapter: Adapter\n meta?: InputMeta\n driver: KubbDriver\n plugin: NormalizedPlugin<TOptions>\n options: TOptions['resolvedOptions']\n resolver: TOptions['resolver']\n}\n\nfunction createMockedPluginContext<TOptions extends PluginFactoryOptions>(opts: RenderGeneratorOptions<TOptions>): Omit<GeneratorContext<TOptions>, 'options'> {\n const root = resolve(opts.config.root, opts.config.output.path)\n\n return {\n config: opts.config,\n root,\n adapter: opts.adapter,\n resolver: opts.resolver,\n plugin: opts.plugin,\n driver: opts.driver,\n getResolver: (name: string) => opts.driver.getResolver(name),\n meta: opts.meta ?? { circularNames: [], enumNames: [] },\n addFile: async (...files: Array<FileNode>) => opts.driver.fileManager.add(...files),\n upsertFile: async (...files: Array<FileNode>) => opts.driver.fileManager.upsert(...files),\n hooks: opts.driver.hooks ?? ({} as never),\n warn: (msg: string) => console.warn(msg),\n error: (msg: string) => console.error(msg),\n info: (msg: string) => console.info(msg),\n } as unknown as Omit<GeneratorContext<TOptions>, 'options'>\n}\n\n/**\n * Renders a generator's `schema` method in a test context.\n *\n * @example\n * ```ts\n * await renderGeneratorSchema(typeGenerator, node, { config, adapter, driver, plugin, options, resolver })\n * await matchFiles(driver.fileManager.files)\n * ```\n */\nexport async function renderGeneratorSchema<TOptions extends PluginFactoryOptions>(\n generator: Generator<TOptions>,\n node: SchemaNode,\n opts: RenderGeneratorOptions<TOptions>,\n): Promise<void> {\n if (!generator.schema) return\n const context = createMockedPluginContext(opts)\n const transformedNode = opts.plugin.macros?.length ? applyMacros(node, opts.plugin.macros) : node\n const result = await generator.schema(transformedNode, {\n ...context,\n options: opts.options,\n })\n await opts.driver.dispatch({ result, renderer: generator.renderer })\n}\n\n/**\n * Renders a generator's `operation` method in a test context.\n *\n * @example\n * ```ts\n * await renderGeneratorOperation(typeGenerator, node, { config, adapter, driver, plugin, options, resolver })\n * await matchFiles(driver.fileManager.files)\n * ```\n */\nexport async function renderGeneratorOperation<TOptions extends PluginFactoryOptions>(\n generator: Generator<TOptions>,\n node: OperationNode,\n opts: RenderGeneratorOptions<TOptions>,\n): Promise<void> {\n if (!generator.operation) return\n const context = createMockedPluginContext(opts)\n const transformedNode = opts.plugin.macros?.length ? applyMacros(node, opts.plugin.macros) : node\n const result = await generator.operation(transformedNode, {\n ...context,\n options: opts.options,\n })\n await opts.driver.dispatch({ result, renderer: generator.renderer })\n}\n\n/**\n * Renders a generator's `operations` method in a test context.\n *\n * @example\n * ```ts\n * await renderGeneratorOperations(classClientGenerator, nodes, { config, adapter, driver, plugin, options, resolver })\n * await matchFiles(driver.fileManager.files)\n * ```\n */\nexport async function renderGeneratorOperations<TOptions extends PluginFactoryOptions>(\n generator: Generator<TOptions>,\n nodes: Array<OperationNode>,\n opts: RenderGeneratorOptions<TOptions>,\n): Promise<void> {\n if (!generator.operations) return\n const context = createMockedPluginContext(opts)\n const transformedNodes = opts.plugin.macros?.length ? nodes.map((n) => applyMacros(n, opts.plugin.macros!)) : nodes\n const result = await generator.operations(transformedNodes, {\n ...context,\n options: opts.options,\n })\n await opts.driver.dispatch({ result, renderer: generator.renderer })\n}\n\ntype MatchFilesOptions = {\n /**\n * Parsers indexed by file extension, used to render each `FileNode` to source.\n * Without a matching parser the file's raw content is used.\n */\n parsers?: Map<FileNode['extname'], Parser>\n /**\n * Formatter applied to non-JSON output before snapshotting, e.g. prettier. When\n * omitted the parsed source is snapshotted as-is.\n */\n format?: (source?: string) => string | Promise<string>\n /**\n * Subfolder under `__snapshots__`, camelCased. Useful to keep variant snapshots apart.\n */\n pre?: string\n}\n\n/**\n * Renders the driver's collected `FileNode`s to source and asserts each against a file snapshot.\n * Pair it with the `renderGenerator*` helpers to snapshot a generator's output.\n *\n * @example\n * ```ts\n * await renderGeneratorSchema(typeGenerator, node, { config, adapter, driver, plugin, options, resolver })\n * await matchFiles(driver.fileManager.files, { parsers, format })\n * ```\n */\nexport async function matchFiles(files: Array<FileNode> | undefined, options: MatchFilesOptions = {}): Promise<Map<string, string> | undefined> {\n if (!files?.length) return\n\n const { parsers = new Map(), format, pre } = options\n const fileProcessor = new FileProcessor({ storage: memoryStorage(), parsers })\n const processed = new Map<string, string>()\n\n for (const file of files) {\n if (!file?.path || processed.has(file.path)) {\n continue\n }\n\n const parsed = fileProcessor.parse(file)\n const code = file.baseName.endsWith('.json') || !format ? parsed : await format(parsed)\n\n processed.set(file.path, code)\n\n const snapshotPath = path.join('__snapshots__', ...(pre ? [camelCase(pre)] : []), file.baseName)\n await expect(code).toMatchFileSnapshot(snapshotPath)\n }\n\n return processed\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"mocks.js","names":[],"sources":["../src/mocks.ts"],"sourcesContent":["import path, { resolve } from 'node:path'\nimport { camelCase } from '@internals/utils'\nimport type { FileNode, InputMeta, Macro, OperationNode, SchemaNode } from '@kubb/ast'\nimport { applyMacros } from '@kubb/ast'\nimport { expect } from 'vitest'\nimport type { Parser } from './defineParser.ts'\nimport { FileManager } from './FileManager.ts'\nimport { FileProcessor } from './FileProcessor.ts'\nimport type { KubbDriver } from './KubbDriver.ts'\nimport { memoryStorage } from './storages/memoryStorage.ts'\nimport type { Adapter, AdapterFactoryOptions, Config, Generator, GeneratorContext, NormalizedPlugin, PluginFactoryOptions, RendererFactory } from './types.ts'\n\n/**\n * Creates a minimal `KubbDriver` mock for unit tests.\n */\nexport function createMockedPluginDriver(options: { name?: string; plugin?: NormalizedPlugin; config?: Config } = {}): KubbDriver {\n const fileManager = new FileManager()\n\n return {\n config: options?.config ?? {\n root: '.',\n output: {\n path: './path',\n },\n },\n getPlugin(_pluginName: string): NormalizedPlugin | undefined {\n return options?.plugin\n },\n getResolver: (_pluginName: string) => options?.plugin?.resolver,\n fileManager,\n async dispatch({ result, renderer }: { result: unknown; renderer?: RendererFactory | null }): Promise<void> {\n if (!result) return\n\n if (Array.isArray(result)) {\n fileManager.upsert(...(result as Array<FileNode>))\n return\n }\n\n if (!renderer) return\n\n using instance = renderer()\n if (instance.stream) {\n for (const file of instance.stream(result)) fileManager.upsert(file)\n return\n }\n\n await instance.render(result)\n fileManager.upsert(...instance.files)\n },\n } as unknown as KubbDriver\n}\n\n/**\n * Creates a minimal `Adapter` mock for unit tests.\n * `parse` returns an empty `InputNode` by default. Override via `options.parse`.\n * `getImports` returns `[]` by default.\n */\nexport function createMockedAdapter<TOptions extends AdapterFactoryOptions = AdapterFactoryOptions>(\n options: {\n name?: TOptions['name']\n resolvedOptions?: TOptions['resolvedOptions']\n parse?: Adapter<TOptions>['parse']\n getImports?: Adapter<TOptions>['getImports']\n } = {},\n): Adapter<TOptions> {\n return {\n name: (options.name ?? 'oas') as TOptions['name'],\n options: (options.resolvedOptions ?? {}) as TOptions['resolvedOptions'],\n parse: options.parse ?? (async () => ({ kind: 'Input' as const, schemas: [], operations: [] })),\n getImports: options.getImports ?? ((_node: SchemaNode, _resolve: (schemaName: string) => { name: string; path: string }) => []),\n } as Adapter<TOptions>\n}\n\n/**\n * Creates a minimal plugin mock for unit tests.\n *\n * @example\n * `const plugin = createMockedPlugin<PluginTs>({ name: '@kubb/plugin-ts', options })`\n */\nexport function createMockedPlugin<TOptions extends PluginFactoryOptions = PluginFactoryOptions>(params: {\n name: TOptions['name']\n options: TOptions['resolvedOptions']\n resolver?: TOptions['resolver']\n macros?: Array<Macro>\n dependencies?: Array<string>\n}): NormalizedPlugin<TOptions> {\n return {\n name: params.name,\n options: params.options,\n resolver: params.resolver,\n macros: params.macros,\n dependencies: params.dependencies,\n hooks: {},\n } as unknown as NormalizedPlugin<TOptions>\n}\n\ntype RenderGeneratorOptions<TOptions extends PluginFactoryOptions> = {\n config: Config\n adapter: Adapter\n meta?: InputMeta\n driver: KubbDriver\n plugin: NormalizedPlugin<TOptions>\n options: TOptions['resolvedOptions']\n resolver: TOptions['resolver']\n}\n\nfunction createMockedPluginContext<TOptions extends PluginFactoryOptions>(opts: RenderGeneratorOptions<TOptions>): Omit<GeneratorContext<TOptions>, 'options'> {\n const root = resolve(opts.config.root, opts.config.output.path)\n\n return {\n config: opts.config,\n root,\n adapter: opts.adapter,\n resolver: opts.resolver,\n plugin: opts.plugin,\n driver: opts.driver,\n getResolver: (name: string) => opts.driver.getResolver(name),\n meta: opts.meta ?? { circularNames: [], enumNames: [] },\n addFile: async (...files: Array<FileNode>) => opts.driver.fileManager.add(...files),\n upsertFile: async (...files: Array<FileNode>) => opts.driver.fileManager.upsert(...files),\n hooks: opts.driver.hooks ?? ({} as never),\n warn: (msg: string) => console.warn(msg),\n error: (msg: string) => console.error(msg),\n info: (msg: string) => console.info(msg),\n } as unknown as Omit<GeneratorContext<TOptions>, 'options'>\n}\n\n/**\n * Renders a generator's `schema` method in a test context.\n *\n * @example\n * ```ts\n * await renderGeneratorSchema(typeGenerator, node, { config, adapter, driver, plugin, options, resolver })\n * await matchFiles(driver.fileManager.files)\n * ```\n */\nexport async function renderGeneratorSchema<TOptions extends PluginFactoryOptions>(\n generator: Generator<TOptions>,\n node: SchemaNode,\n opts: RenderGeneratorOptions<TOptions>,\n): Promise<void> {\n if (!generator.schema) return\n const context = createMockedPluginContext(opts)\n const transformedNode = opts.plugin.macros?.length ? applyMacros(node, opts.plugin.macros) : node\n const result = await generator.schema(transformedNode, {\n ...context,\n options: opts.options,\n })\n await opts.driver.dispatch({ result, renderer: generator.renderer })\n}\n\n/**\n * Renders a generator's `operation` method in a test context.\n *\n * @example\n * ```ts\n * await renderGeneratorOperation(typeGenerator, node, { config, adapter, driver, plugin, options, resolver })\n * await matchFiles(driver.fileManager.files)\n * ```\n */\nexport async function renderGeneratorOperation<TOptions extends PluginFactoryOptions>(\n generator: Generator<TOptions>,\n node: OperationNode,\n opts: RenderGeneratorOptions<TOptions>,\n): Promise<void> {\n if (!generator.operation) return\n const context = createMockedPluginContext(opts)\n const transformedNode = opts.plugin.macros?.length ? applyMacros(node, opts.plugin.macros) : node\n const result = await generator.operation(transformedNode, {\n ...context,\n options: opts.options,\n })\n await opts.driver.dispatch({ result, renderer: generator.renderer })\n}\n\n/**\n * Renders a generator's `operations` method in a test context.\n *\n * @example\n * ```ts\n * await renderGeneratorOperations(classClientGenerator, nodes, { config, adapter, driver, plugin, options, resolver })\n * await matchFiles(driver.fileManager.files)\n * ```\n */\nexport async function renderGeneratorOperations<TOptions extends PluginFactoryOptions>(\n generator: Generator<TOptions>,\n nodes: Array<OperationNode>,\n opts: RenderGeneratorOptions<TOptions>,\n): Promise<void> {\n if (!generator.operations) return\n const context = createMockedPluginContext(opts)\n const transformedNodes = opts.plugin.macros?.length ? nodes.map((n) => applyMacros(n, opts.plugin.macros!)) : nodes\n const result = await generator.operations(transformedNodes, {\n ...context,\n options: opts.options,\n })\n await opts.driver.dispatch({ result, renderer: generator.renderer })\n}\n\ntype MatchFilesOptions = {\n /**\n * Parsers indexed by file extension, used to render each `FileNode` to source.\n * Without a matching parser the file's raw content is used.\n */\n parsers?: Map<FileNode['extname'], Parser>\n /**\n * Formatter applied to non-JSON output before snapshotting, e.g. prettier. When\n * omitted the parsed source is snapshotted as-is.\n */\n format?: (source?: string) => string | Promise<string>\n /**\n * Subfolder under `__snapshots__`, camelCased. Useful to keep variant snapshots apart.\n */\n pre?: string\n}\n\n/**\n * Renders the driver's collected `FileNode`s to source and asserts each against a file snapshot.\n * Pair it with the `renderGenerator*` helpers to snapshot a generator's output.\n *\n * @example\n * ```ts\n * await renderGeneratorSchema(typeGenerator, node, { config, adapter, driver, plugin, options, resolver })\n * await matchFiles(driver.fileManager.files, { parsers, format })\n * ```\n */\nexport async function matchFiles(files: Array<FileNode> | undefined, options: MatchFilesOptions = {}): Promise<Map<string, string> | undefined> {\n if (!files?.length) return\n\n const { parsers = new Map(), format, pre } = options\n const fileProcessor = new FileProcessor({ storage: memoryStorage(), parsers })\n const processed = new Map<string, string>()\n\n for (const file of files) {\n if (!file?.path || processed.has(file.path)) {\n continue\n }\n\n const parsed = await fileProcessor.parse(file)\n const code = file.baseName.endsWith('.json') || !format ? parsed : await format(parsed)\n\n processed.set(file.path, code)\n\n const snapshotPath = path.join('__snapshots__', ...(pre ? [camelCase(pre)] : []), file.baseName)\n await expect(code).toMatchFileSnapshot(snapshotPath)\n }\n\n return processed\n}\n"],"mappings":";;;;;;;;;AAeA,SAAgB,yBAAyB,UAAyE,CAAC,GAAe;CAChI,MAAM,cAAc,IAAI,YAAY;CAEpC,OAAO;EACL,QAAQ,SAAS,UAAU;GACzB,MAAM;GACN,QAAQ,EACN,MAAM,SACR;EACF;EACA,UAAU,aAAmD;GAC3D,OAAO,SAAS;EAClB;EACA,cAAc,gBAAwB,SAAS,QAAQ;EACvD;EACA,MAAM,SAAS,EAAE,QAAQ,YAAmF;;;IAC1G,IAAI,CAAC,QAAQ;IAEb,IAAI,MAAM,QAAQ,MAAM,GAAG;KACzB,YAAY,OAAO,GAAI,MAA0B;KACjD;IACF;IAEA,IAAI,CAAC,UAAU;IAEf,MAAM,WAAA,YAAA,EAAW,SAAS,CAAA;IAC1B,IAAI,SAAS,QAAQ;KACnB,KAAK,MAAM,QAAQ,SAAS,OAAO,MAAM,GAAG,YAAY,OAAO,IAAI;KACnE;IACF;IAEA,MAAM,SAAS,OAAO,MAAM;IAC5B,YAAY,OAAO,GAAG,SAAS,KAAK;;;;;;EACtC;CACF;AACF;;;;;;AAOA,SAAgB,oBACd,UAKI,CAAC,GACc;CACnB,OAAO;EACL,MAAO,QAAQ,QAAQ;EACvB,SAAU,QAAQ,mBAAmB,CAAC;EACtC,OAAO,QAAQ,UAAU,aAAa;GAAE,MAAM;GAAkB,SAAS,CAAC;GAAG,YAAY,CAAC;EAAE;EAC5F,YAAY,QAAQ,gBAAgB,OAAmB,aAAqE,CAAC;CAC/H;AACF;;;;;;;AAQA,SAAgB,mBAAiF,QAMlE;CAC7B,OAAO;EACL,MAAM,OAAO;EACb,SAAS,OAAO;EAChB,UAAU,OAAO;EACjB,QAAQ,OAAO;EACf,cAAc,OAAO;EACrB,OAAO,CAAC;CACV;AACF;AAYA,SAAS,0BAAiE,MAAqF;CAC7J,MAAM,OAAO,QAAQ,KAAK,OAAO,MAAM,KAAK,OAAO,OAAO,IAAI;CAE9D,OAAO;EACL,QAAQ,KAAK;EACb;EACA,SAAS,KAAK;EACd,UAAU,KAAK;EACf,QAAQ,KAAK;EACb,QAAQ,KAAK;EACb,cAAc,SAAiB,KAAK,OAAO,YAAY,IAAI;EAC3D,MAAM,KAAK,QAAQ;GAAE,eAAe,CAAC;GAAG,WAAW,CAAC;EAAE;EACtD,SAAS,OAAO,GAAG,UAA2B,KAAK,OAAO,YAAY,IAAI,GAAG,KAAK;EAClF,YAAY,OAAO,GAAG,UAA2B,KAAK,OAAO,YAAY,OAAO,GAAG,KAAK;EACxF,OAAO,KAAK,OAAO,SAAU,CAAC;EAC9B,OAAO,QAAgB,QAAQ,KAAK,GAAG;EACvC,QAAQ,QAAgB,QAAQ,MAAM,GAAG;EACzC,OAAO,QAAgB,QAAQ,KAAK,GAAG;CACzC;AACF;;;;;;;;;;AAWA,eAAsB,sBACpB,WACA,MACA,MACe;CACf,IAAI,CAAC,UAAU,QAAQ;CACvB,MAAM,UAAU,0BAA0B,IAAI;CAC9C,MAAM,kBAAkB,KAAK,OAAO,QAAQ,SAAS,YAAY,MAAM,KAAK,OAAO,MAAM,IAAI;CAC7F,MAAM,SAAS,MAAM,UAAU,OAAO,iBAAiB;EACrD,GAAG;EACH,SAAS,KAAK;CAChB,CAAC;CACD,MAAM,KAAK,OAAO,SAAS;EAAE;EAAQ,UAAU,UAAU;CAAS,CAAC;AACrE;;;;;;;;;;AAWA,eAAsB,yBACpB,WACA,MACA,MACe;CACf,IAAI,CAAC,UAAU,WAAW;CAC1B,MAAM,UAAU,0BAA0B,IAAI;CAC9C,MAAM,kBAAkB,KAAK,OAAO,QAAQ,SAAS,YAAY,MAAM,KAAK,OAAO,MAAM,IAAI;CAC7F,MAAM,SAAS,MAAM,UAAU,UAAU,iBAAiB;EACxD,GAAG;EACH,SAAS,KAAK;CAChB,CAAC;CACD,MAAM,KAAK,OAAO,SAAS;EAAE;EAAQ,UAAU,UAAU;CAAS,CAAC;AACrE;;;;;;;;;;AAWA,eAAsB,0BACpB,WACA,OACA,MACe;CACf,IAAI,CAAC,UAAU,YAAY;CAC3B,MAAM,UAAU,0BAA0B,IAAI;CAC9C,MAAM,mBAAmB,KAAK,OAAO,QAAQ,SAAS,MAAM,KAAK,MAAM,YAAY,GAAG,KAAK,OAAO,MAAO,CAAC,IAAI;CAC9G,MAAM,SAAS,MAAM,UAAU,WAAW,kBAAkB;EAC1D,GAAG;EACH,SAAS,KAAK;CAChB,CAAC;CACD,MAAM,KAAK,OAAO,SAAS;EAAE;EAAQ,UAAU,UAAU;CAAS,CAAC;AACrE;;;;;;;;;;;AA6BA,eAAsB,WAAW,OAAoC,UAA6B,CAAC,GAA6C;CAC9I,IAAI,CAAC,OAAO,QAAQ;CAEpB,MAAM,EAAE,0BAAU,IAAI,IAAI,GAAG,QAAQ,QAAQ;CAC7C,MAAM,gBAAgB,IAAI,cAAc;EAAE,SAAS,cAAc;EAAG;CAAQ,CAAC;CAC7E,MAAM,4BAAY,IAAI,IAAoB;CAE1C,KAAK,MAAM,QAAQ,OAAO;EACxB,IAAI,CAAC,MAAM,QAAQ,UAAU,IAAI,KAAK,IAAI,GACxC;EAGF,MAAM,SAAS,MAAM,cAAc,MAAM,IAAI;EAC7C,MAAM,OAAO,KAAK,SAAS,SAAS,OAAO,KAAK,CAAC,SAAS,SAAS,MAAM,OAAO,MAAM;EAEtF,UAAU,IAAI,KAAK,MAAM,IAAI;EAE7B,MAAM,eAAe,KAAK,KAAK,iBAAiB,GAAI,MAAM,CAAC,UAAU,GAAG,CAAC,IAAI,CAAC,GAAI,KAAK,QAAQ;EAC/F,MAAM,OAAO,IAAI,CAAC,CAAC,oBAAoB,YAAY;CACrD;CAEA,OAAO;AACT"}
package/package.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@kubb/core",
3
- "version": "5.0.0-beta.67",
4
- "description": "Core engine for Kubb's plugin-based code generation system. Provides the plugin driver, file manager, defineConfig, and build orchestration used by every Kubb plugin.",
3
+ "version": "5.0.0-beta.69",
4
+ "description": "Core engine for Kubb. Provides the plugin driver, file manager, defineConfig, and build orchestration used by every Kubb plugin.",
5
5
  "keywords": [
6
6
  "code-generator",
7
7
  "codegen",
8
8
  "kubb",
9
- "openapi",
9
+ "meta-framework",
10
10
  "plugin-system",
11
11
  "typescript"
12
12
  ],
@@ -55,14 +55,14 @@
55
55
  "registry": "https://registry.npmjs.org/"
56
56
  },
57
57
  "dependencies": {
58
- "@kubb/ast": "5.0.0-beta.67"
58
+ "@kubb/ast": "5.0.0-beta.69"
59
59
  },
60
60
  "devDependencies": {
61
61
  "@internals/utils": "0.0.0",
62
- "@kubb/renderer-jsx": "5.0.0-beta.67"
62
+ "@kubb/renderer-jsx": "5.0.0-beta.69"
63
63
  },
64
64
  "peerDependencies": {
65
- "@kubb/renderer-jsx": "5.0.0-beta.67"
65
+ "@kubb/renderer-jsx": "5.0.0-beta.69"
66
66
  },
67
67
  "engines": {
68
68
  "node": ">=22"