@kubb/core 5.0.0-beta.20 → 5.0.0-beta.21

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.
package/dist/index.js CHANGED
@@ -1,187 +1,9 @@
1
1
  import "./chunk--u3MIqq1.js";
2
- import { a as definePlugin, c as DEFAULT_STUDIO_URL, d as forBatches, f as isPromise, i as defineResolver, l as logLevel, n as applyHookResult, o as DEFAULT_BANNER, p as withDrain, r as FileManager, s as DEFAULT_EXTENSION, t as KubbDriver, u as URLPath } from "./KubbDriver-Cxii_rBp.js";
3
- import { EventEmitter } from "node:events";
2
+ import { a as FileManager, c as DEFAULT_BANNER, d as logLevel, f as URLPath, i as FileProcessor, l as DEFAULT_EXTENSION, m as BuildError, o as defineResolver, p as AsyncEventEmitter, r as _usingCtx, s as definePlugin, t as KubbDriver, u as DEFAULT_STUDIO_URL } from "./KubbDriver-Cq1isv2P.js";
4
3
  import { access, mkdir, readFile, readdir, rm, writeFile } from "node:fs/promises";
5
4
  import { dirname, join, resolve } from "node:path";
6
5
  import * as ast from "@kubb/ast";
7
- import { collectUsedSchemaNames, extractStringsFromNodes, transform } from "@kubb/ast";
8
6
  import { version } from "node:process";
9
- //#region ../../internals/utils/src/errors.ts
10
- /**
11
- * Thrown when one or more errors occur during a Kubb build.
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
- * ```
18
- */
19
- var BuildError = class extends Error {
20
- errors;
21
- constructor(message, options) {
22
- super(message, { cause: options.cause });
23
- this.name = "BuildError";
24
- this.errors = options.errors;
25
- }
26
- };
27
- /**
28
- * Coerces an unknown thrown value to an `Error` instance.
29
- * Returns the value as-is when it is already an `Error`; otherwise wraps it with `String(value)`.
30
- *
31
- * @example
32
- * ```ts
33
- * try { ... } catch(err) {
34
- * throw new BuildError('Build failed', { cause: toError(err), errors: [] })
35
- * }
36
- * ```
37
- */
38
- function toError(value) {
39
- return value instanceof Error ? value : new Error(String(value));
40
- }
41
- //#endregion
42
- //#region ../../internals/utils/src/asyncEventEmitter.ts
43
- /**
44
- * Typed `EventEmitter` that awaits all async listeners before resolving.
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
- * ```
53
- */
54
- var AsyncEventEmitter = class {
55
- /**
56
- * Maximum number of listeners per event before Node emits a memory-leak warning.
57
- * @default 10
58
- */
59
- constructor(maxListener = 10) {
60
- this.#emitter.setMaxListeners(maxListener);
61
- }
62
- #emitter = new EventEmitter();
63
- /**
64
- * Emits `eventName` and awaits all registered listeners sequentially.
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
- * ```
71
- */
72
- emit(eventName, ...eventArgs) {
73
- const listeners = this.#emitter.listeners(eventName);
74
- if (listeners.length === 0) return;
75
- return this.#emitAll(eventName, listeners, eventArgs);
76
- }
77
- async #emitAll(eventName, listeners, eventArgs) {
78
- for (const listener of listeners) try {
79
- await listener(...eventArgs);
80
- } catch (err) {
81
- let serializedArgs;
82
- try {
83
- serializedArgs = JSON.stringify(eventArgs);
84
- } catch {
85
- serializedArgs = String(eventArgs);
86
- }
87
- throw new Error(`Error in async listener for "${eventName}" with eventArgs ${serializedArgs}`, { cause: toError(err) });
88
- }
89
- }
90
- /**
91
- * Registers a persistent listener for `eventName`.
92
- *
93
- * @example
94
- * ```ts
95
- * emitter.on('build', async (name) => { console.log(name) })
96
- * ```
97
- */
98
- on(eventName, handler) {
99
- this.#emitter.on(eventName, handler);
100
- }
101
- /**
102
- * Registers a one-shot listener that removes itself after the first invocation.
103
- *
104
- * @example
105
- * ```ts
106
- * emitter.onOnce('build', async (name) => { console.log(name) })
107
- * ```
108
- */
109
- onOnce(eventName, handler) {
110
- const wrapper = (...args) => {
111
- this.off(eventName, wrapper);
112
- return handler(...args);
113
- };
114
- this.on(eventName, wrapper);
115
- }
116
- /**
117
- * Removes a previously registered listener.
118
- *
119
- * @example
120
- * ```ts
121
- * emitter.off('build', handler)
122
- * ```
123
- */
124
- off(eventName, handler) {
125
- this.#emitter.off(eventName, handler);
126
- }
127
- /**
128
- * Returns the number of listeners registered for `eventName`.
129
- *
130
- * @example
131
- * ```ts
132
- * emitter.on('build', handler)
133
- * emitter.listenerCount('build') // 1
134
- * ```
135
- */
136
- listenerCount(eventName) {
137
- return this.#emitter.listenerCount(eventName);
138
- }
139
- /**
140
- * Removes all listeners from every event channel.
141
- *
142
- * @example
143
- * ```ts
144
- * emitter.removeAll()
145
- * ```
146
- */
147
- removeAll() {
148
- this.#emitter.removeAllListeners();
149
- }
150
- };
151
- //#endregion
152
- //#region ../../internals/utils/src/time.ts
153
- /**
154
- * Calculates elapsed time in milliseconds from a high-resolution `process.hrtime` start time.
155
- * Rounds to 2 decimal places for sub-millisecond precision without noise.
156
- *
157
- * @example
158
- * ```ts
159
- * const start = process.hrtime()
160
- * doWork()
161
- * getElapsedMs(start) // 42.35
162
- * ```
163
- */
164
- function getElapsedMs(hrStart) {
165
- const [seconds, nanoseconds] = process.hrtime(hrStart);
166
- const ms = seconds * 1e3 + nanoseconds / 1e6;
167
- return Math.round(ms * 100) / 100;
168
- }
169
- /**
170
- * Converts a millisecond duration into a human-readable string (`ms`, `s`, or `m s`).
171
- *
172
- * @example
173
- * ```ts
174
- * formatMs(250) // '250ms'
175
- * formatMs(1500) // '1.50s'
176
- * formatMs(90000) // '1m 30.0s'
177
- * ```
178
- */
179
- function formatMs(ms) {
180
- if (ms >= 6e4) return `${Math.floor(ms / 6e4)}m ${(ms % 6e4 / 1e3).toFixed(1)}s`;
181
- if (ms >= 1e3) return `${(ms / 1e3).toFixed(2)}s`;
182
- return `${Math.round(ms)}ms`;
183
- }
184
- //#endregion
185
7
  //#region ../../internals/utils/src/fs.ts
186
8
  /**
187
9
  * Resolves to `true` when the file or directory at `path` exists.
@@ -279,7 +101,7 @@ function createAdapter(build) {
279
101
  }
280
102
  //#endregion
281
103
  //#region package.json
282
- var version$1 = "5.0.0-beta.20";
104
+ var version$1 = "5.0.0-beta.21";
283
105
  //#endregion
284
106
  //#region src/createStorage.ts
285
107
  /**
@@ -318,62 +140,6 @@ function createStorage(build) {
318
140
  return (options) => build(options ?? {});
319
141
  }
320
142
  //#endregion
321
- //#region src/FileProcessor.ts
322
- function joinSources(file) {
323
- const sources = file.sources;
324
- if (sources.length === 0) return "";
325
- const parts = [];
326
- for (const source of sources) {
327
- const s = extractStringsFromNodes(source.nodes);
328
- if (s) parts.push(s);
329
- }
330
- return parts.join("\n\n");
331
- }
332
- /**
333
- * Converts a single file to a string using the registered parsers.
334
- * Falls back to joining source values when no matching parser is found.
335
- *
336
- * @internal
337
- */
338
- var FileProcessor = class {
339
- events = new AsyncEventEmitter();
340
- parse(file, { parsers, extension } = {}) {
341
- const parseExtName = extension?.[file.extname] || void 0;
342
- if (!parsers || !file.extname) return joinSources(file);
343
- const parser = parsers.get(file.extname);
344
- if (!parser) return joinSources(file);
345
- return parser.parse(file, { extname: parseExtName });
346
- }
347
- *stream(files, options = {}) {
348
- const total = files.length;
349
- if (total === 0) return;
350
- let processed = 0;
351
- for (const file of files) {
352
- const source = this.parse(file, options);
353
- processed++;
354
- yield {
355
- file,
356
- source,
357
- processed,
358
- total,
359
- percentage: processed / total * 100
360
- };
361
- }
362
- }
363
- async run(files, options = {}) {
364
- await this.events.emit("start", files);
365
- for (const { file, source, processed, total, percentage } of this.stream(files, options)) await this.events.emit("update", {
366
- file,
367
- source,
368
- processed,
369
- percentage,
370
- total
371
- });
372
- await this.events.emit("end", files);
373
- return files;
374
- }
375
- };
376
- //#endregion
377
143
  //#region src/storages/fsStorage.ts
378
144
  /**
379
145
  * Built-in filesystem storage driver.
@@ -448,74 +214,12 @@ const fsStorage = createStorage(() => ({
448
214
  }
449
215
  }));
450
216
  //#endregion
451
- //#region \0@oxc-project+runtime@0.129.0/helpers/usingCtx.js
452
- function _usingCtx() {
453
- var r = "function" == typeof SuppressedError ? SuppressedError : function(r, e) {
454
- var n = Error();
455
- return n.name = "SuppressedError", n.error = r, n.suppressed = e, n;
456
- };
457
- var e = {};
458
- var n = [];
459
- function using(r, e) {
460
- if (null != e) {
461
- if (Object(e) !== e) throw new TypeError("using declarations can only be used with objects, functions, null, or undefined.");
462
- if (r) var o = e[Symbol.asyncDispose || Symbol["for"]("Symbol.asyncDispose")];
463
- if (void 0 === o && (o = e[Symbol.dispose || Symbol["for"]("Symbol.dispose")], r)) var t = o;
464
- if ("function" != typeof o) throw new TypeError("Object is not disposable.");
465
- t && (o = function o() {
466
- try {
467
- t.call(e);
468
- } catch (r) {
469
- return Promise.reject(r);
470
- }
471
- }), n.push({
472
- v: e,
473
- d: o,
474
- a: r
475
- });
476
- } else r && n.push({
477
- d: e,
478
- a: r
479
- });
480
- return e;
481
- }
482
- return {
483
- e,
484
- u: using.bind(null, !1),
485
- a: using.bind(null, !0),
486
- d: function d() {
487
- var o;
488
- var t = this.e;
489
- var s = 0;
490
- function next() {
491
- for (; o = n.pop();) try {
492
- if (!o.a && 1 === s) return s = 0, n.push(o), Promise.resolve().then(next);
493
- if (o.d) {
494
- var r = o.d.call(o.v);
495
- if (o.a) return s |= 2, Promise.resolve(r).then(next, err);
496
- } else s |= 1;
497
- } catch (r) {
498
- return err(r);
499
- }
500
- if (1 === s) return t !== e ? Promise.reject(t) : Promise.resolve();
501
- if (t !== e) throw t;
502
- }
503
- function err(n) {
504
- return t = t !== e ? new r(n, t) : n, next();
505
- }
506
- return next();
507
- }
508
- };
509
- }
510
- //#endregion
511
217
  //#region src/createKubb.ts
512
218
  /**
513
219
  * Builds a `Storage` view scoped to the file paths produced by the current build.
514
- *
515
- * Reads delegate to the underlying `storage` (typically `fsStorage()`) so source bytes
516
- * stay where they were written instead of being held in an extra in-memory map.
517
- * Writing via `setItem` stores the content in the underlying storage and registers the
518
- * key so subsequent reads and `getKeys` are scoped to this build's output.
220
+ * Reads delegate to the underlying `storage` so source bytes stay where they were
221
+ * written; writes register the key so subsequent reads and `getKeys` are scoped
222
+ * to this build's output.
519
223
  */
520
224
  function createSourcesView(storage) {
521
225
  const paths = /* @__PURE__ */ new Set();
@@ -547,9 +251,8 @@ function createSourcesView(storage) {
547
251
  }
548
252
  }))();
549
253
  }
550
- async function setup(userConfig, options = {}) {
551
- const hooks = options.hooks ?? new AsyncEventEmitter();
552
- const config = {
254
+ function resolveConfig(userConfig) {
255
+ return {
553
256
  ...userConfig,
554
257
  root: userConfig.root || process.cwd(),
555
258
  parsers: userConfig.parsers ?? [],
@@ -567,422 +270,6 @@ async function setup(userConfig, options = {}) {
567
270
  } : void 0,
568
271
  plugins: userConfig.plugins ?? []
569
272
  };
570
- const driver = new KubbDriver(config, { hooks });
571
- const storage = createSourcesView(config.storage);
572
- const diagnosticInfo = getDiagnosticInfo();
573
- await hooks.emit("kubb:debug", {
574
- date: /* @__PURE__ */ new Date(),
575
- logs: [
576
- "Configuration:",
577
- ` • Name: ${userConfig.name || "unnamed"}`,
578
- ` • Root: ${userConfig.root || process.cwd()}`,
579
- ` • Output: ${userConfig.output?.path || "not specified"}`,
580
- ` • Plugins: ${userConfig.plugins?.length || 0}`,
581
- "Output Settings:",
582
- ` • Storage: ${config.storage.name}`,
583
- ` • Formatter: ${userConfig.output?.format || "none"}`,
584
- ` • Linter: ${userConfig.output?.lint || "none"}`,
585
- `Running adapter: ${config.adapter?.name || "none"}`,
586
- "Environment:",
587
- Object.entries(diagnosticInfo).map(([key, value]) => ` • ${key}: ${value}`).join("\n")
588
- ]
589
- });
590
- try {
591
- if (isInputPath(userConfig) && !new URLPath(userConfig.input.path).isURL) {
592
- await exists(userConfig.input.path);
593
- await hooks.emit("kubb:debug", {
594
- date: /* @__PURE__ */ new Date(),
595
- logs: [`✓ Input file validated: ${userConfig.input.path}`]
596
- });
597
- }
598
- } catch (caughtError) {
599
- if (isInputPath(userConfig)) {
600
- const error = caughtError;
601
- 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 });
602
- }
603
- }
604
- if (config.output.clean) {
605
- await hooks.emit("kubb:debug", {
606
- date: /* @__PURE__ */ new Date(),
607
- logs: ["Cleaning output directories", ` • Output: ${config.output.path}`]
608
- });
609
- await config.storage.clear(resolve(config.root, config.output.path));
610
- }
611
- await driver.setup();
612
- return {
613
- config,
614
- hooks,
615
- driver,
616
- storage,
617
- dispose,
618
- [Symbol.dispose]: dispose
619
- };
620
- function dispose() {
621
- driver.dispose();
622
- }
623
- }
624
- async function safeBuild(setupResult) {
625
- try {
626
- var _usingCtx$1 = _usingCtx();
627
- _usingCtx$1.u(setupResult);
628
- const { driver, hooks, storage } = setupResult;
629
- const failedPlugins = /* @__PURE__ */ new Set();
630
- const pluginTimings = /* @__PURE__ */ new Map();
631
- const config = driver.config;
632
- const writtenPaths = /* @__PURE__ */ new Set();
633
- const parsersMap = /* @__PURE__ */ new Map();
634
- const fileProcessor = new FileProcessor();
635
- for (const parser of config.parsers) if (parser.extNames) for (const extname of parser.extNames) parsersMap.set(extname, parser);
636
- async function flushPendingFiles() {
637
- const files = driver.fileManager.files.filter((f) => !writtenPaths.has(f.path));
638
- if (files.length === 0) return;
639
- await hooks.emit("kubb:debug", {
640
- date: /* @__PURE__ */ new Date(),
641
- logs: [`Writing ${files.length} files...`]
642
- });
643
- await hooks.emit("kubb:files:processing:start", { files });
644
- const stream = fileProcessor.stream(files, {
645
- parsers: parsersMap,
646
- extension: config.output.extension
647
- });
648
- const queue = [];
649
- for (const { file, source, processed, total, percentage } of stream) {
650
- writtenPaths.add(file.path);
651
- queue.push((async () => {
652
- await hooks.emit("kubb:file:processing:update", {
653
- file,
654
- source,
655
- processed,
656
- total,
657
- percentage,
658
- config
659
- });
660
- if (source) await storage.setItem(file.path, source);
661
- })());
662
- if (queue.length >= 50) await Promise.all(queue.splice(0));
663
- }
664
- await Promise.all(queue);
665
- await hooks.emit("kubb:files:processing:end", { files });
666
- await hooks.emit("kubb:debug", {
667
- date: /* @__PURE__ */ new Date(),
668
- logs: [`✓ File write process completed for ${files.length} files`]
669
- });
670
- }
671
- async function dispatchOperationsToGenerators(generators, collectedOperations, ctx, rendererFor) {
672
- for (const gen of generators) {
673
- if (!gen.operations) continue;
674
- await applyHookResult({
675
- result: await gen.operations(collectedOperations, ctx),
676
- driver,
677
- rendererFactory: rendererFor(gen)
678
- });
679
- }
680
- await driver.hooks.emit("kubb:generate:operations", collectedOperations, ctx);
681
- }
682
- /**
683
- * Single-pass fan-out: iterates all schemas and operations once, distributing each node
684
- * to every generator-plugin in parallel. This replaces the N-pass-per-plugin pattern
685
- * (each plugin getting its own iterator) with one parse pass fanned to all plugins,
686
- * eliminating the N×parse-time overhead for multi-plugin builds.
687
- */
688
- async function runPlugins(entries) {
689
- const { schemas, operations } = driver.inputNode;
690
- const operationFilterTypes = new Set([
691
- "tag",
692
- "operationId",
693
- "path",
694
- "method",
695
- "contentType"
696
- ]);
697
- const states = entries.map(({ plugin, context, hrStart }) => {
698
- const { exclude, include, override } = plugin.options;
699
- const hasExclude = Array.isArray(exclude) && exclude.length > 0;
700
- const hasInclude = Array.isArray(include) && include.length > 0;
701
- const hasOverride = Array.isArray(override) && override.length > 0;
702
- return {
703
- plugin,
704
- generatorContext: {
705
- ...context,
706
- resolver: driver.getResolver(plugin.name)
707
- },
708
- generators: plugin.generators ?? [],
709
- hrStart,
710
- failed: false,
711
- error: void 0,
712
- optionsAreStatic: !hasExclude && !hasInclude && !hasOverride,
713
- allowedSchemaNames: void 0
714
- };
715
- });
716
- const pruningStates = states.filter(({ plugin }) => {
717
- const { include } = plugin.options;
718
- return (include?.some(({ type }) => operationFilterTypes.has(type)) ?? false) && !(include?.some(({ type }) => type === "schemaName") ?? false);
719
- });
720
- if (pruningStates.length > 0) {
721
- const allSchemas = [];
722
- for await (const schema of schemas) allSchemas.push(schema);
723
- const includedOpsByState = new Map(pruningStates.map((s) => [s, []]));
724
- for await (const operation of operations) for (const state of pruningStates) {
725
- const { exclude, include, override } = state.plugin.options;
726
- if (state.generatorContext.resolver.resolveOptions(operation, {
727
- options: state.plugin.options,
728
- exclude,
729
- include,
730
- override
731
- }) !== null) includedOpsByState.get(state)?.push(operation);
732
- }
733
- for (const state of pruningStates) state.allowedSchemaNames = collectUsedSchemaNames(includedOpsByState.get(state) ?? [], allSchemas);
734
- }
735
- function resolveRendererFor(gen, state) {
736
- return gen.renderer === null ? void 0 : gen.renderer ?? state.plugin.renderer ?? state.generatorContext.config.renderer;
737
- }
738
- async function dispatchSchema(state, node) {
739
- if (state.failed) return;
740
- try {
741
- const { plugin, generatorContext, generators } = state;
742
- const transformedNode = plugin.transformer ? transform(node, plugin.transformer) : node;
743
- if (state.allowedSchemaNames !== void 0 && transformedNode.name && !state.allowedSchemaNames.has(transformedNode.name)) return;
744
- const { exclude, include, override } = plugin.options;
745
- const options = state.optionsAreStatic ? plugin.options : generatorContext.resolver.resolveOptions(transformedNode, {
746
- options: plugin.options,
747
- exclude,
748
- include,
749
- override
750
- });
751
- if (options === null) return;
752
- const ctx = {
753
- ...generatorContext,
754
- options
755
- };
756
- for (const gen of generators) {
757
- if (!gen.schema) continue;
758
- const raw = gen.schema(transformedNode, ctx);
759
- const applied = applyHookResult({
760
- result: isPromise(raw) ? await raw : raw,
761
- driver,
762
- rendererFactory: resolveRendererFor(gen, state)
763
- });
764
- if (isPromise(applied)) await applied;
765
- }
766
- await driver.hooks.emit("kubb:generate:schema", transformedNode, ctx);
767
- } catch (caughtError) {
768
- state.failed = true;
769
- state.error = caughtError;
770
- }
771
- }
772
- async function dispatchOperation(state, node) {
773
- if (state.failed) return;
774
- try {
775
- const { plugin, generatorContext, generators } = state;
776
- const transformedNode = plugin.transformer ? transform(node, plugin.transformer) : node;
777
- const { exclude, include, override } = plugin.options;
778
- const options = state.optionsAreStatic ? plugin.options : generatorContext.resolver.resolveOptions(transformedNode, {
779
- options: plugin.options,
780
- exclude,
781
- include,
782
- override
783
- });
784
- if (options === null) return;
785
- const ctx = {
786
- ...generatorContext,
787
- options
788
- };
789
- for (const gen of generators) {
790
- if (!gen.operation) continue;
791
- const raw = gen.operation(transformedNode, ctx);
792
- const applied = applyHookResult({
793
- result: isPromise(raw) ? await raw : raw,
794
- driver,
795
- rendererFactory: resolveRendererFor(gen, state)
796
- });
797
- if (isPromise(applied)) await applied;
798
- }
799
- await driver.hooks.emit("kubb:generate:operation", transformedNode, ctx);
800
- } catch (caughtError) {
801
- state.failed = true;
802
- state.error = caughtError;
803
- }
804
- }
805
- await forBatches(schemas, (nodes) => Promise.all(nodes.flatMap((n) => states.map((state) => dispatchSchema(state, n)))), {
806
- concurrency: 8,
807
- flush: flushPendingFiles
808
- });
809
- const collectedOperations = [];
810
- await forBatches(operations, (nodes) => {
811
- collectedOperations.push(...nodes);
812
- return Promise.all(nodes.flatMap((n) => states.map((state) => dispatchOperation(state, n))));
813
- }, {
814
- concurrency: 8,
815
- flush: flushPendingFiles
816
- });
817
- for (const state of states) {
818
- if (!state.failed) try {
819
- const { plugin, generatorContext, generators } = state;
820
- await dispatchOperationsToGenerators(generators, collectedOperations, {
821
- ...generatorContext,
822
- options: plugin.options
823
- }, (gen) => resolveRendererFor(gen, state));
824
- } catch (caughtError) {
825
- state.failed = true;
826
- state.error = caughtError;
827
- }
828
- const duration = getElapsedMs(state.hrStart);
829
- pluginTimings.set(state.plugin.name, duration);
830
- await driver.hooks.emit("kubb:plugin:end", {
831
- plugin: state.plugin,
832
- duration,
833
- success: !state.failed,
834
- ...state.failed && state.error ? { error: state.error } : {},
835
- config: driver.config,
836
- get files() {
837
- return driver.fileManager.files;
838
- },
839
- upsertFile: (...files) => driver.fileManager.upsert(...files)
840
- });
841
- if (state.failed && state.error) failedPlugins.add({
842
- plugin: state.plugin,
843
- error: state.error
844
- });
845
- await driver.hooks.emit("kubb:debug", {
846
- date: /* @__PURE__ */ new Date(),
847
- logs: [state.failed ? "✗ Plugin start failed" : `✓ Plugin started successfully (${formatMs(duration)})`]
848
- });
849
- }
850
- }
851
- try {
852
- await driver.emitSetupHooks();
853
- if (driver.adapter && driver.inputNode) await hooks.emit("kubb:build:start", {
854
- config,
855
- adapter: driver.adapter,
856
- meta: driver.inputNode.meta,
857
- getPlugin: driver.getPlugin.bind(driver),
858
- get files() {
859
- return driver.fileManager.files;
860
- },
861
- upsertFile: (...files) => driver.fileManager.upsert(...files)
862
- });
863
- const generatorPlugins = [];
864
- for (const plugin of driver.plugins.values()) {
865
- const context = driver.getContext(plugin);
866
- const hrStart = process.hrtime();
867
- try {
868
- await hooks.emit("kubb:plugin:start", { plugin });
869
- await hooks.emit("kubb:debug", {
870
- date: /* @__PURE__ */ new Date(),
871
- logs: ["Starting plugin...", ` • Plugin Name: ${plugin.name}`]
872
- });
873
- } catch (caughtError) {
874
- const error = caughtError;
875
- const duration = getElapsedMs(hrStart);
876
- pluginTimings.set(plugin.name, duration);
877
- await hooks.emit("kubb:plugin:end", {
878
- plugin,
879
- duration,
880
- success: false,
881
- error,
882
- config,
883
- get files() {
884
- return driver.fileManager.files;
885
- },
886
- upsertFile: (...files) => driver.fileManager.upsert(...files)
887
- });
888
- failedPlugins.add({
889
- plugin,
890
- error
891
- });
892
- continue;
893
- }
894
- if (plugin.generators?.length || driver.hasEventGenerators(plugin.name)) {
895
- generatorPlugins.push({
896
- plugin,
897
- context,
898
- hrStart
899
- });
900
- continue;
901
- }
902
- const duration = getElapsedMs(hrStart);
903
- pluginTimings.set(plugin.name, duration);
904
- await hooks.emit("kubb:plugin:end", {
905
- plugin,
906
- duration,
907
- success: true,
908
- config,
909
- get files() {
910
- return driver.fileManager.files;
911
- },
912
- upsertFile: (...files) => driver.fileManager.upsert(...files)
913
- });
914
- await hooks.emit("kubb:debug", {
915
- date: /* @__PURE__ */ new Date(),
916
- logs: [`✓ Plugin started successfully (${formatMs(duration)})`]
917
- });
918
- }
919
- if (generatorPlugins.length > 0) if (driver.inputNode) await withDrain(() => runPlugins(generatorPlugins), flushPendingFiles);
920
- else for (const { plugin, hrStart } of generatorPlugins) {
921
- const duration = getElapsedMs(hrStart);
922
- pluginTimings.set(plugin.name, duration);
923
- await hooks.emit("kubb:plugin:end", {
924
- plugin,
925
- duration,
926
- success: true,
927
- config,
928
- get files() {
929
- return driver.fileManager.files;
930
- },
931
- upsertFile: (...files) => driver.fileManager.upsert(...files)
932
- });
933
- }
934
- await hooks.emit("kubb:plugins:end", {
935
- config,
936
- get files() {
937
- return driver.fileManager.files;
938
- },
939
- upsertFile: (...files) => driver.fileManager.upsert(...files)
940
- });
941
- await flushPendingFiles();
942
- const files = driver.fileManager.files;
943
- await hooks.emit("kubb:build:end", {
944
- files,
945
- config,
946
- outputDir: resolve(config.root, config.output.path)
947
- });
948
- return {
949
- failedPlugins,
950
- files,
951
- driver,
952
- pluginTimings,
953
- storage
954
- };
955
- } catch (error) {
956
- return {
957
- failedPlugins,
958
- files: [],
959
- driver,
960
- pluginTimings,
961
- error,
962
- storage
963
- };
964
- }
965
- } catch (_) {
966
- _usingCtx$1.e = _;
967
- } finally {
968
- _usingCtx$1.d();
969
- }
970
- }
971
- async function build(setupResult) {
972
- const { files, driver, failedPlugins, pluginTimings, error, storage } = await safeBuild(setupResult);
973
- if (error) throw error;
974
- if (failedPlugins.size > 0) {
975
- const errors = [...failedPlugins].map(({ error }) => error);
976
- throw new BuildError(`Build Error with ${failedPlugins.size} failed plugins`, { errors });
977
- }
978
- return {
979
- failedPlugins,
980
- files,
981
- driver,
982
- pluginTimings,
983
- error: void 0,
984
- storage
985
- };
986
273
  }
987
274
  /**
988
275
  * Returns a snapshot of the current runtime environment.
@@ -1003,56 +290,143 @@ function isInputPath(config) {
1003
290
  return typeof config?.input === "object" && config.input !== null && "path" in config.input;
1004
291
  }
1005
292
  /**
1006
- * Creates a Kubb instance bound to a single config entry.
293
+ * Kubb code-generation instance bound to a single config entry. Resolves the user
294
+ * config during `setup()` and shares `hooks`, `storage`, `driver`, and `config` across
295
+ * the `setup → build` lifecycle.
1007
296
  *
1008
- * Accepts a user-facing config shape and resolves it to a full {@link Config} during
1009
- * `setup()`. The instance then holds shared state (`hooks`, `storage`, `driver`, `config`)
1010
- * across the `setup → build` lifecycle. Attach event listeners to `kubb.hooks` before
1011
- * calling `setup()` or `build()`.
297
+ * Attach event listeners to `.hooks` before calling `setup()` or `build()`.
1012
298
  *
1013
299
  * @example
1014
300
  * ```ts
1015
301
  * const kubb = createKubb(userConfig)
1016
- *
1017
- * kubb.hooks.on('kubb:plugin:end', ({ plugin, duration }) => {
1018
- * console.log(`${plugin.name} completed in ${duration}ms`)
1019
- * })
1020
- *
302
+ * kubb.hooks.on('kubb:plugin:end', ({ plugin, duration }) => console.log(plugin.name, duration))
1021
303
  * const { files, failedPlugins } = await kubb.safeBuild()
1022
304
  * ```
1023
305
  */
1024
- function createKubb(userConfig, options = {}) {
1025
- const hooks = options.hooks ?? new AsyncEventEmitter();
1026
- let setupResult;
1027
- const instance = {
1028
- get hooks() {
1029
- return hooks;
1030
- },
1031
- get storage() {
1032
- if (!setupResult) throw new Error("[kubb] setup() must be called before accessing storage");
1033
- return setupResult.storage;
1034
- },
1035
- get driver() {
1036
- if (!setupResult) throw new Error("[kubb] setup() must be called before accessing driver");
1037
- return setupResult.driver;
1038
- },
1039
- get config() {
1040
- if (!setupResult) throw new Error("[kubb] setup() must be called before accessing config");
1041
- return setupResult.config;
1042
- },
1043
- async setup() {
1044
- setupResult = await setup(userConfig, { hooks });
1045
- },
1046
- async build() {
1047
- if (!setupResult) await instance.setup();
1048
- return build(setupResult);
1049
- },
1050
- async safeBuild() {
1051
- if (!setupResult) await instance.setup();
1052
- return safeBuild(setupResult);
306
+ var Kubb = class {
307
+ hooks;
308
+ #userConfig;
309
+ #config = null;
310
+ #driver = null;
311
+ #storage = null;
312
+ constructor(userConfig, options = {}) {
313
+ this.#userConfig = userConfig;
314
+ this.hooks = options.hooks ?? new AsyncEventEmitter();
315
+ }
316
+ get storage() {
317
+ if (!this.#storage) throw new Error("[kubb] setup() must be called before accessing storage");
318
+ return this.#storage;
319
+ }
320
+ get driver() {
321
+ if (!this.#driver) throw new Error("[kubb] setup() must be called before accessing driver");
322
+ return this.#driver;
323
+ }
324
+ get config() {
325
+ if (!this.#config) throw new Error("[kubb] setup() must be called before accessing config");
326
+ return this.#config;
327
+ }
328
+ /**
329
+ * Resolves config and initializes the driver. `build()` calls this automatically.
330
+ */
331
+ async setup() {
332
+ const config = resolveConfig(this.#userConfig);
333
+ const driver = new KubbDriver(config, { hooks: this.hooks });
334
+ const storage = createSourcesView(config.storage);
335
+ await this.hooks.emit("kubb:debug", {
336
+ date: /* @__PURE__ */ new Date(),
337
+ logs: this.#configLogs(config)
338
+ });
339
+ if (isInputPath(this.#userConfig) && !new URLPath(this.#userConfig.input.path).isURL) try {
340
+ await exists(this.#userConfig.input.path);
341
+ await this.hooks.emit("kubb:debug", {
342
+ date: /* @__PURE__ */ new Date(),
343
+ logs: [`✓ Input file validated: ${this.#userConfig.input.path}`]
344
+ });
345
+ } catch (caughtError) {
346
+ throw new Error(`Cannot read file/URL defined in \`input.path\` or set with \`kubb generate PATH\` in the CLI of your Kubb config ${this.#userConfig.input.path}`, { cause: caughtError });
1053
347
  }
1054
- };
1055
- return instance;
348
+ if (config.output.clean) {
349
+ await this.hooks.emit("kubb:debug", {
350
+ date: /* @__PURE__ */ new Date(),
351
+ logs: ["Cleaning output directories", ` • Output: ${config.output.path}`]
352
+ });
353
+ await config.storage.clear(resolve(config.root, config.output.path));
354
+ }
355
+ await driver.setup();
356
+ this.#config = config;
357
+ this.#driver = driver;
358
+ this.#storage = storage;
359
+ }
360
+ /**
361
+ * Runs the full pipeline and throws on any plugin error.
362
+ * Automatically calls `setup()` if needed.
363
+ */
364
+ async build() {
365
+ const out = await this.safeBuild();
366
+ if (out.error) throw out.error;
367
+ if (out.failedPlugins.size > 0) {
368
+ const errors = [...out.failedPlugins].map(({ error }) => error);
369
+ throw new BuildError(`Build Error with ${out.failedPlugins.size} failed plugins`, { errors });
370
+ }
371
+ return out;
372
+ }
373
+ /**
374
+ * Runs the full pipeline and captures errors in `BuildOutput` instead of throwing.
375
+ * Automatically calls `setup()` if needed.
376
+ */
377
+ async safeBuild() {
378
+ try {
379
+ var _usingCtx$1 = _usingCtx();
380
+ if (!this.#driver) await this.setup();
381
+ const cleanup = _usingCtx$1.u(this);
382
+ const driver = cleanup.driver;
383
+ const storage = cleanup.storage;
384
+ const { failedPlugins, pluginTimings, error } = await driver.run({ storage });
385
+ return {
386
+ failedPlugins,
387
+ files: driver.fileManager.files,
388
+ driver,
389
+ pluginTimings,
390
+ storage,
391
+ ...error ? { error } : {}
392
+ };
393
+ } catch (_) {
394
+ _usingCtx$1.e = _;
395
+ } finally {
396
+ _usingCtx$1.d();
397
+ }
398
+ }
399
+ dispose() {
400
+ this.#driver?.dispose();
401
+ }
402
+ [Symbol.dispose]() {
403
+ this.dispose();
404
+ }
405
+ #configLogs(config) {
406
+ const u = this.#userConfig;
407
+ const diag = getDiagnosticInfo();
408
+ return [
409
+ "Configuration:",
410
+ ` • Name: ${u.name || "unnamed"}`,
411
+ ` • Root: ${u.root || process.cwd()}`,
412
+ ` • Output: ${u.output?.path || "not specified"}`,
413
+ ` • Plugins: ${u.plugins?.length || 0}`,
414
+ "Output Settings:",
415
+ ` • Storage: ${config.storage.name}`,
416
+ ` • Formatter: ${u.output?.format || "none"}`,
417
+ ` • Linter: ${u.output?.lint || "none"}`,
418
+ `Running adapter: ${config.adapter?.name || "none"}`,
419
+ "Environment:",
420
+ Object.entries(diag).map(([key, value]) => ` • ${key}: ${value}`).join("\n")
421
+ ];
422
+ }
423
+ };
424
+ /**
425
+ * Factory for {@link Kubb}. Equivalent to `new Kubb(userConfig, options)` and kept
426
+ * as the canonical public entry point.
427
+ */
428
+ function createKubb(userConfig, options = {}) {
429
+ return new Kubb(userConfig, options);
1056
430
  }
1057
431
  //#endregion
1058
432
  //#region src/createRenderer.ts
@@ -1066,6 +440,7 @@ function createKubb(userConfig, options = {}) {
1066
440
  * return {
1067
441
  * async render(element) { await runtime.render(element) },
1068
442
  * get files() { return runtime.nodes },
443
+ * dispose() { runtime.unmount() },
1069
444
  * unmount(error) { runtime.unmount(error) },
1070
445
  * }
1071
446
  * })