@kubb/core 5.0.0-beta.4 → 5.0.0-beta.41

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. package/README.md +25 -158
  2. package/dist/diagnostics-Ba-FcsPo.d.ts +2970 -0
  3. package/dist/index.cjs +760 -1193
  4. package/dist/index.cjs.map +1 -1
  5. package/dist/index.d.ts +165 -171
  6. package/dist/index.js +745 -1188
  7. package/dist/index.js.map +1 -1
  8. package/dist/memoryStorage-DA1bnMte.js +2860 -0
  9. package/dist/memoryStorage-DA1bnMte.js.map +1 -0
  10. package/dist/memoryStorage-DZqlEW7H.cjs +2993 -0
  11. package/dist/memoryStorage-DZqlEW7H.cjs.map +1 -0
  12. package/dist/mocks.cjs +80 -18
  13. package/dist/mocks.cjs.map +1 -1
  14. package/dist/mocks.d.ts +35 -8
  15. package/dist/mocks.js +81 -21
  16. package/dist/mocks.js.map +1 -1
  17. package/package.json +8 -19
  18. package/src/FileManager.ts +85 -63
  19. package/src/FileProcessor.ts +171 -43
  20. package/src/HookRegistry.ts +45 -0
  21. package/src/KubbDriver.ts +906 -0
  22. package/src/Telemetry.ts +278 -0
  23. package/src/Transform.ts +58 -0
  24. package/src/constants.ts +111 -19
  25. package/src/createAdapter.ts +113 -17
  26. package/src/createKubb.ts +944 -492
  27. package/src/createRenderer.ts +58 -27
  28. package/src/createReporter.ts +147 -0
  29. package/src/createStorage.ts +36 -23
  30. package/src/defineGenerator.ts +129 -17
  31. package/src/defineLogger.ts +58 -5
  32. package/src/defineMiddleware.ts +19 -17
  33. package/src/defineParser.ts +30 -13
  34. package/src/definePlugin.ts +320 -17
  35. package/src/defineResolver.ts +381 -179
  36. package/src/diagnostics.ts +660 -0
  37. package/src/index.ts +8 -6
  38. package/src/mocks.ts +92 -19
  39. package/src/reporters/cliReporter.ts +90 -0
  40. package/src/reporters/fileReporter.ts +103 -0
  41. package/src/reporters/jsonReporter.ts +20 -0
  42. package/src/reporters/report.ts +85 -0
  43. package/src/storages/fsStorage.ts +13 -37
  44. package/src/types.ts +60 -1297
  45. package/dist/PluginDriver-Ds-Us-e4.cjs +0 -1036
  46. package/dist/PluginDriver-Ds-Us-e4.cjs.map +0 -1
  47. package/dist/PluginDriver-Wi34Pegx.js +0 -945
  48. package/dist/PluginDriver-Wi34Pegx.js.map +0 -1
  49. package/dist/types-Cd0jhNmx.d.ts +0 -2153
  50. package/src/Kubb.ts +0 -300
  51. package/src/PluginDriver.ts +0 -424
  52. package/src/devtools.ts +0 -59
  53. package/src/renderNode.ts +0 -35
  54. package/src/utils/diagnostics.ts +0 -18
  55. package/src/utils/isInputPath.ts +0 -10
  56. package/src/utils/packageJSON.ts +0 -99
  57. /package/dist/{chunk--u3MIqq1.js → chunk-C0LytTxp.js} +0 -0
package/dist/index.cjs CHANGED
@@ -1,200 +1,87 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- const require_PluginDriver = require("./PluginDriver-Ds-Us-e4.cjs");
3
- let node_events = require("node:events");
2
+ const require_memoryStorage = require("./memoryStorage-DZqlEW7H.cjs");
3
+ let node_util = require("node:util");
4
+ let node_crypto = require("node:crypto");
4
5
  let node_fs_promises = require("node:fs/promises");
5
6
  let node_path = require("node:path");
7
+ let node_dns = require("node:dns");
6
8
  let _kubb_ast = require("@kubb/ast");
7
- _kubb_ast = require_PluginDriver.__toESM(_kubb_ast, 1);
9
+ _kubb_ast = require_memoryStorage.__toESM(_kubb_ast, 1);
8
10
  let node_process = require("node:process");
9
- //#region ../../internals/utils/src/errors.ts
11
+ node_process = require_memoryStorage.__toESM(node_process, 1);
12
+ let node_os = require("node:os");
13
+ node_os = require_memoryStorage.__toESM(node_os, 1);
14
+ //#region ../../internals/utils/src/colors.ts
10
15
  /**
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
- * ```
16
+ * Parses a CSS hex color string (`#RGB`) into its RGB channels.
17
+ * Falls back to `255` for any channel that cannot be parsed.
18
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));
19
+ function parseHex(color) {
20
+ const int = Number.parseInt(color.replace("#", ""), 16);
21
+ return Number.isNaN(int) ? {
22
+ r: 255,
23
+ g: 255,
24
+ b: 255
25
+ } : {
26
+ r: int >> 16 & 255,
27
+ g: int >> 8 & 255,
28
+ b: int & 255
29
+ };
40
30
  }
41
- //#endregion
42
- //#region ../../internals/utils/src/asyncEventEmitter.ts
43
31
  /**
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
- * ```
32
+ * Returns a function that wraps a string in a 24-bit ANSI true-color escape sequence
33
+ * for the given hex color.
53
34
  */
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 node_events.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
- async emit(eventName, ...eventArgs) {
73
- const listeners = this.#emitter.listeners(eventName);
74
- if (listeners.length === 0) return;
75
- for (const listener of listeners) try {
76
- await listener(...eventArgs);
77
- } catch (err) {
78
- let serializedArgs;
79
- try {
80
- serializedArgs = JSON.stringify(eventArgs);
81
- } catch {
82
- serializedArgs = String(eventArgs);
83
- }
84
- throw new Error(`Error in async listener for "${eventName}" with eventArgs ${serializedArgs}`, { cause: toError(err) });
85
- }
86
- }
87
- /**
88
- * Registers a persistent listener for `eventName`.
89
- *
90
- * @example
91
- * ```ts
92
- * emitter.on('build', async (name) => { console.log(name) })
93
- * ```
94
- */
95
- on(eventName, handler) {
96
- this.#emitter.on(eventName, handler);
97
- }
98
- /**
99
- * Registers a one-shot listener that removes itself after the first invocation.
100
- *
101
- * @example
102
- * ```ts
103
- * emitter.onOnce('build', async (name) => { console.log(name) })
104
- * ```
105
- */
106
- onOnce(eventName, handler) {
107
- const wrapper = (...args) => {
108
- this.off(eventName, wrapper);
109
- return handler(...args);
110
- };
111
- this.on(eventName, wrapper);
112
- }
113
- /**
114
- * Removes a previously registered listener.
115
- *
116
- * @example
117
- * ```ts
118
- * emitter.off('build', handler)
119
- * ```
120
- */
121
- off(eventName, handler) {
122
- this.#emitter.off(eventName, handler);
123
- }
124
- /**
125
- * Returns the number of listeners registered for `eventName`.
126
- *
127
- * @example
128
- * ```ts
129
- * emitter.on('build', handler)
130
- * emitter.listenerCount('build') // 1
131
- * ```
132
- */
133
- listenerCount(eventName) {
134
- return this.#emitter.listenerCount(eventName);
135
- }
136
- /**
137
- * Removes all listeners from every event channel.
138
- *
139
- * @example
140
- * ```ts
141
- * emitter.removeAll()
142
- * ```
143
- */
144
- removeAll() {
145
- this.#emitter.removeAllListeners();
146
- }
147
- };
148
- //#endregion
149
- //#region ../../internals/utils/src/time.ts
35
+ function hex(color) {
36
+ const { r, g, b } = parseHex(color);
37
+ return (text) => `\x1b[38;2;${r};${g};${b}m${text}\x1b[0m`;
38
+ }
39
+ hex("#F55A17"), hex("#F5A217"), hex("#F58517"), hex("#B45309"), hex("#FFFFFF"), hex("#adadc6"), hex("#FDA4AF");
150
40
  /**
151
- * Calculates elapsed time in milliseconds from a high-resolution `process.hrtime` start time.
152
- * Rounds to 2 decimal places for sub-millisecond precision without noise.
153
- *
154
- * @example
155
- * ```ts
156
- * const start = process.hrtime()
157
- * doWork()
158
- * getElapsedMs(start) // 42.35
159
- * ```
41
+ * ANSI color names used by {@link randomCliColor} for deterministic terminal coloring.
160
42
  */
161
- function getElapsedMs(hrStart) {
162
- const [seconds, nanoseconds] = process.hrtime(hrStart);
163
- const ms = seconds * 1e3 + nanoseconds / 1e6;
164
- return Math.round(ms * 100) / 100;
165
- }
43
+ const randomColors = [
44
+ "black",
45
+ "red",
46
+ "green",
47
+ "yellow",
48
+ "blue",
49
+ "white",
50
+ "magenta",
51
+ "cyan",
52
+ "gray"
53
+ ];
166
54
  /**
167
- * Converts a millisecond duration into a human-readable string (`ms`, `s`, or `m s`).
55
+ * Wraps `text` in a deterministic ANSI color derived from the text's SHA-256 hash.
168
56
  *
169
57
  * @example
170
58
  * ```ts
171
- * formatMs(250) // '250ms'
172
- * formatMs(1500) // '1.50s'
173
- * formatMs(90000) // '1m 30.0s'
59
+ * randomCliColor('petstore') // '\x1b[33m' + 'petstore' + '\x1b[39m' (always the same color for 'petstore')
174
60
  * ```
175
61
  */
176
- function formatMs(ms) {
177
- if (ms >= 6e4) return `${Math.floor(ms / 6e4)}m ${(ms % 6e4 / 1e3).toFixed(1)}s`;
178
- if (ms >= 1e3) return `${(ms / 1e3).toFixed(2)}s`;
179
- return `${Math.round(ms)}ms`;
62
+ function randomCliColor(text) {
63
+ if (!text) return "";
64
+ return (0, node_util.styleText)(randomColors[(0, node_crypto.createHash)("sha256").update(text).digest().readUInt32BE(0) % randomColors.length] ?? "white", text);
180
65
  }
181
66
  //#endregion
182
- //#region ../../internals/utils/src/fs.ts
67
+ //#region ../../internals/utils/src/env.ts
183
68
  /**
184
- * Resolves to `true` when the file or directory at `path` exists.
185
- * Uses `Bun.file().exists()` when running under Bun, `fs.access` otherwise.
69
+ * Returns `true` when the process is running in a CI environment.
70
+ * Covers GitHub Actions, GitLab CI, CircleCI, Travis CI, Jenkins, Bitbucket,
71
+ * TeamCity, Buildkite, and Azure Pipelines.
186
72
  *
187
73
  * @example
188
74
  * ```ts
189
- * if (await exists('./kubb.config.ts')) {
190
- * const content = await read('./kubb.config.ts')
75
+ * if (isCIEnvironment()) {
76
+ * logger.level = 'error'
191
77
  * }
192
78
  * ```
193
79
  */
194
- async function exists(path) {
195
- if (typeof Bun !== "undefined") return Bun.file(path).exists();
196
- return (0, node_fs_promises.access)(path).then(() => true, () => false);
80
+ function isCIEnvironment() {
81
+ return !!(process.env.CI || process.env.GITHUB_ACTIONS || process.env.GITLAB_CI || process.env.BITBUCKET_BUILD_NUMBER || process.env.JENKINS_URL || process.env.CIRCLECI || process.env.TRAVIS || process.env.TEAMCITY_VERSION || process.env.BUILDKITE || process.env.TF_BUILD);
197
82
  }
83
+ //#endregion
84
+ //#region ../../internals/utils/src/fs.ts
198
85
  /**
199
86
  * Writes `data` to `path`, trimming leading/trailing whitespace before saving.
200
87
  * Skips the write when the trimmed content is empty or identical to what is already on disk.
@@ -245,502 +132,88 @@ async function clean(path) {
245
132
  });
246
133
  }
247
134
  //#endregion
248
- //#region ../../internals/utils/src/reserved.ts
135
+ //#region ../../internals/utils/src/network.ts
249
136
  /**
250
- * JavaScript and Java reserved words.
251
- * @link https://github.com/jonschlinkert/reserved/blob/master/index.js
137
+ * Well-known stable domains used as DNS probes to check internet connectivity.
252
138
  */
253
- const reservedWords = new Set([
254
- "abstract",
255
- "arguments",
256
- "boolean",
257
- "break",
258
- "byte",
259
- "case",
260
- "catch",
261
- "char",
262
- "class",
263
- "const",
264
- "continue",
265
- "debugger",
266
- "default",
267
- "delete",
268
- "do",
269
- "double",
270
- "else",
271
- "enum",
272
- "eval",
273
- "export",
274
- "extends",
275
- "false",
276
- "final",
277
- "finally",
278
- "float",
279
- "for",
280
- "function",
281
- "goto",
282
- "if",
283
- "implements",
284
- "import",
285
- "in",
286
- "instanceof",
287
- "int",
288
- "interface",
289
- "let",
290
- "long",
291
- "native",
292
- "new",
293
- "null",
294
- "package",
295
- "private",
296
- "protected",
297
- "public",
298
- "return",
299
- "short",
300
- "static",
301
- "super",
302
- "switch",
303
- "synchronized",
304
- "this",
305
- "throw",
306
- "throws",
307
- "transient",
308
- "true",
309
- "try",
310
- "typeof",
311
- "var",
312
- "void",
313
- "volatile",
314
- "while",
315
- "with",
316
- "yield",
317
- "Array",
318
- "Date",
319
- "hasOwnProperty",
320
- "Infinity",
321
- "isFinite",
322
- "isNaN",
323
- "isPrototypeOf",
324
- "length",
325
- "Math",
326
- "name",
327
- "NaN",
328
- "Number",
329
- "Object",
330
- "prototype",
331
- "String",
332
- "toString",
333
- "undefined",
334
- "valueOf"
335
- ]);
139
+ const TEST_DOMAINS = [
140
+ "dns.google.com",
141
+ "cloudflare.com",
142
+ "one.one.one.one"
143
+ ];
336
144
  /**
337
- * Returns `true` when `name` is a syntactically valid JavaScript variable name.
145
+ * Returns `true` when the system has internet connectivity.
146
+ * Probes DNS resolution against well-known stable domains.
338
147
  *
339
148
  * @example
340
149
  * ```ts
341
- * isValidVarName('status') // true
342
- * isValidVarName('class') // false (reserved word)
343
- * isValidVarName('42foo') // false (starts with digit)
150
+ * if (await isOnline()) {
151
+ * await fetchLatestVersion()
152
+ * }
344
153
  * ```
345
154
  */
346
- function isValidVarName(name) {
347
- if (!name || reservedWords.has(name)) return false;
348
- return /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(name);
155
+ async function isOnline() {
156
+ for (const domain of TEST_DOMAINS) try {
157
+ await node_dns.promises.resolve(domain);
158
+ return true;
159
+ } catch {}
160
+ return false;
349
161
  }
350
- //#endregion
351
- //#region ../../internals/utils/src/urlPath.ts
352
162
  /**
353
- * Parses and transforms an OpenAPI/Swagger path string into various URL formats.
354
- *
355
- * @example
356
- * const p = new URLPath('/pet/{petId}')
357
- * p.URL // '/pet/:petId'
358
- * p.template // '`/pet/${petId}`'
359
- */
360
- var URLPath = class {
361
- /**
362
- * The raw OpenAPI/Swagger path string, e.g. `/pet/{petId}`.
363
- */
364
- path;
365
- #options;
366
- constructor(path, options = {}) {
367
- this.path = path;
368
- this.#options = options;
369
- }
370
- /** Converts the OpenAPI path to Express-style colon syntax, e.g. `/pet/{petId}` → `/pet/:petId`.
371
- *
372
- * @example
373
- * ```ts
374
- * new URLPath('/pet/{petId}').URL // '/pet/:petId'
375
- * ```
376
- */
377
- get URL() {
378
- return this.toURLPath();
379
- }
380
- /** Returns `true` when `path` is a fully-qualified URL (e.g. starts with `https://`).
381
- *
382
- * @example
383
- * ```ts
384
- * new URLPath('https://petstore.swagger.io/v2/pet').isURL // true
385
- * new URLPath('/pet/{petId}').isURL // false
386
- * ```
387
- */
388
- get isURL() {
389
- try {
390
- return !!new URL(this.path).href;
391
- } catch {
392
- return false;
393
- }
394
- }
395
- /**
396
- * Converts the OpenAPI path to a TypeScript template literal string.
397
- *
398
- * @example
399
- * new URLPath('/pet/{petId}').template // '`/pet/${petId}`'
400
- * new URLPath('/account/monetary-accountID').template // '`/account/${monetaryAccountId}`'
401
- */
402
- get template() {
403
- return this.toTemplateString();
404
- }
405
- /** Returns the path and its extracted params as a structured `URLObject`, or as a stringified expression when `stringify` is set.
406
- *
407
- * @example
408
- * ```ts
409
- * new URLPath('/pet/{petId}').object
410
- * // { url: '/pet/:petId', params: { petId: 'petId' } }
411
- * ```
412
- */
413
- get object() {
414
- return this.toObject();
415
- }
416
- /** Returns a map of path parameter names, or `undefined` when the path has no parameters.
417
- *
418
- * @example
419
- * ```ts
420
- * new URLPath('/pet/{petId}').params // { petId: 'petId' }
421
- * new URLPath('/pet').params // undefined
422
- * ```
423
- */
424
- get params() {
425
- return this.getParams();
426
- }
427
- #transformParam(raw) {
428
- const param = isValidVarName(raw) ? raw : require_PluginDriver.camelCase(raw);
429
- return this.#options.casing === "camelcase" ? require_PluginDriver.camelCase(param) : param;
430
- }
431
- /**
432
- * Iterates over every `{param}` token in `path`, calling `fn` with the raw token and transformed name.
433
- */
434
- #eachParam(fn) {
435
- for (const match of this.path.matchAll(/\{([^}]+)\}/g)) {
436
- const raw = match[1];
437
- fn(raw, this.#transformParam(raw));
438
- }
439
- }
440
- toObject({ type = "path", replacer, stringify } = {}) {
441
- const object = {
442
- url: type === "path" ? this.toURLPath() : this.toTemplateString({ replacer }),
443
- params: this.getParams()
444
- };
445
- if (stringify) {
446
- if (type === "template") return JSON.stringify(object).replaceAll("'", "").replaceAll(`"`, "");
447
- if (object.params) return `{ url: '${object.url}', params: ${JSON.stringify(object.params).replaceAll("'", "").replaceAll(`"`, "")} }`;
448
- return `{ url: '${object.url}' }`;
449
- }
450
- return object;
451
- }
452
- /**
453
- * Converts the OpenAPI path to a TypeScript template literal string.
454
- * An optional `replacer` can transform each extracted parameter name before interpolation.
455
- *
456
- * @example
457
- * new URLPath('/pet/{petId}').toTemplateString() // '`/pet/${petId}`'
458
- */
459
- toTemplateString({ prefix = "", replacer } = {}) {
460
- return `\`${prefix}${this.path.split(/\{([^}]+)\}/).map((part, i) => {
461
- if (i % 2 === 0) return part;
462
- const param = this.#transformParam(part);
463
- return `\${${replacer ? replacer(param) : param}}`;
464
- }).join("")}\``;
465
- }
466
- /**
467
- * Extracts all `{param}` segments from the path and returns them as a key-value map.
468
- * An optional `replacer` transforms each parameter name in both key and value positions.
469
- * Returns `undefined` when no path parameters are found.
470
- *
471
- * @example
472
- * ```ts
473
- * new URLPath('/pet/{petId}/tag/{tagId}').getParams()
474
- * // { petId: 'petId', tagId: 'tagId' }
475
- * ```
476
- */
477
- getParams(replacer) {
478
- const params = {};
479
- this.#eachParam((_raw, param) => {
480
- const key = replacer ? replacer(param) : param;
481
- params[key] = key;
482
- });
483
- return Object.keys(params).length > 0 ? params : void 0;
484
- }
485
- /** Converts the OpenAPI path to Express-style colon syntax.
486
- *
487
- * @example
488
- * ```ts
489
- * new URLPath('/pet/{petId}').toURLPath() // '/pet/:petId'
490
- * ```
491
- */
492
- toURLPath() {
493
- return this.path.replace(/\{([^}]+)\}/g, ":$1");
494
- }
495
- };
496
- //#endregion
497
- //#region src/createAdapter.ts
498
- /**
499
- * Factory for implementing custom adapters that translate non-OpenAPI specs into Kubb's AST.
500
- *
501
- * Use this to support GraphQL schemas, gRPC definitions, AsyncAPI, or custom domain-specific languages.
502
- * Built-in adapters include `@kubb/adapter-oas` for OpenAPI and Swagger documents.
503
- *
504
- * @note Adapters must parse their input format to Kubb's `InputNode` structure.
163
+ * Executes `fn` only when the system is online. Returns `null` when offline or on error.
505
164
  *
506
165
  * @example
507
166
  * ```ts
508
- * export const myAdapter = createAdapter<MyAdapter>((options) => {
509
- * return {
510
- * name: 'my-adapter',
511
- * options,
512
- * async parse(source) {
513
- * // Transform source format to InputNode
514
- * return { ... }
515
- * },
516
- * }
517
- * })
518
- *
519
- * // Instantiate:
520
- * const adapter = myAdapter({ validate: true })
167
+ * const version = await executeIfOnline(() => fetchLatestVersion('kubb'))
168
+ * // null when offline
521
169
  * ```
522
170
  */
523
- function createAdapter(build) {
524
- return (options) => build(options ?? {});
525
- }
526
- //#endregion
527
- //#region ../../node_modules/.pnpm/yocto-queue@1.2.2/node_modules/yocto-queue/index.js
528
- var Node = class {
529
- value;
530
- next;
531
- constructor(value) {
532
- this.value = value;
533
- }
534
- };
535
- var Queue = class {
536
- #head;
537
- #tail;
538
- #size;
539
- constructor() {
540
- this.clear();
541
- }
542
- enqueue(value) {
543
- const node = new Node(value);
544
- if (this.#head) {
545
- this.#tail.next = node;
546
- this.#tail = node;
547
- } else {
548
- this.#head = node;
549
- this.#tail = node;
550
- }
551
- this.#size++;
552
- }
553
- dequeue() {
554
- const current = this.#head;
555
- if (!current) return;
556
- this.#head = this.#head.next;
557
- this.#size--;
558
- if (!this.#head) this.#tail = void 0;
559
- return current.value;
560
- }
561
- peek() {
562
- if (!this.#head) return;
563
- return this.#head.value;
564
- }
565
- clear() {
566
- this.#head = void 0;
567
- this.#tail = void 0;
568
- this.#size = 0;
569
- }
570
- get size() {
571
- return this.#size;
572
- }
573
- *[Symbol.iterator]() {
574
- let current = this.#head;
575
- while (current) {
576
- yield current.value;
577
- current = current.next;
578
- }
579
- }
580
- *drain() {
581
- while (this.#head) yield this.dequeue();
171
+ async function executeIfOnline(fn) {
172
+ if (!await isOnline()) return null;
173
+ try {
174
+ return await fn();
175
+ } catch {
176
+ return null;
582
177
  }
583
- };
584
- //#endregion
585
- //#region ../../node_modules/.pnpm/p-limit@7.3.0/node_modules/p-limit/index.js
586
- function pLimit(concurrency) {
587
- let rejectOnClear = false;
588
- if (typeof concurrency === "object") ({concurrency, rejectOnClear = false} = concurrency);
589
- validateConcurrency(concurrency);
590
- if (typeof rejectOnClear !== "boolean") throw new TypeError("Expected `rejectOnClear` to be a boolean");
591
- const queue = new Queue();
592
- let activeCount = 0;
593
- const resumeNext = () => {
594
- if (activeCount < concurrency && queue.size > 0) {
595
- activeCount++;
596
- queue.dequeue().run();
597
- }
598
- };
599
- const next = () => {
600
- activeCount--;
601
- resumeNext();
602
- };
603
- const run = async (function_, resolve, arguments_) => {
604
- const result = (async () => function_(...arguments_))();
605
- resolve(result);
606
- try {
607
- await result;
608
- } catch {}
609
- next();
610
- };
611
- const enqueue = (function_, resolve, reject, arguments_) => {
612
- const queueItem = { reject };
613
- new Promise((internalResolve) => {
614
- queueItem.run = internalResolve;
615
- queue.enqueue(queueItem);
616
- }).then(run.bind(void 0, function_, resolve, arguments_));
617
- if (activeCount < concurrency) resumeNext();
618
- };
619
- const generator = (function_, ...arguments_) => new Promise((resolve, reject) => {
620
- enqueue(function_, resolve, reject, arguments_);
621
- });
622
- Object.defineProperties(generator, {
623
- activeCount: { get: () => activeCount },
624
- pendingCount: { get: () => queue.size },
625
- clearQueue: { value() {
626
- if (!rejectOnClear) {
627
- queue.clear();
628
- return;
629
- }
630
- const abortError = AbortSignal.abort().reason;
631
- while (queue.size > 0) queue.dequeue().reject(abortError);
632
- } },
633
- concurrency: {
634
- get: () => concurrency,
635
- set(newConcurrency) {
636
- validateConcurrency(newConcurrency);
637
- concurrency = newConcurrency;
638
- queueMicrotask(() => {
639
- while (activeCount < concurrency && queue.size > 0) resumeNext();
640
- });
641
- }
642
- },
643
- map: { async value(iterable, function_) {
644
- const promises = Array.from(iterable, (value, index) => this(function_, value, index));
645
- return Promise.all(promises);
646
- } }
647
- });
648
- return generator;
649
- }
650
- function validateConcurrency(concurrency) {
651
- if (!((Number.isInteger(concurrency) || concurrency === Number.POSITIVE_INFINITY) && concurrency > 0)) throw new TypeError("Expected `concurrency` to be a number from 1 and up");
652
178
  }
653
179
  //#endregion
654
- //#region src/FileProcessor.ts
655
- function joinSources(file) {
656
- return file.sources.map((item) => (0, _kubb_ast.extractStringsFromNodes)(item.nodes)).filter(Boolean).join("\n\n");
657
- }
658
- /**
659
- * Converts a single file to a string using the registered parsers.
660
- * Falls back to joining source values when no matching parser is found.
661
- *
662
- * @internal
663
- */
664
- var FileProcessor = class {
665
- #limit = pLimit(100);
666
- async parse(file, { parsers, extension } = {}) {
667
- const parseExtName = extension?.[file.extname] || void 0;
668
- if (!parsers || !file.extname) return joinSources(file);
669
- const parser = parsers.get(file.extname);
670
- if (!parser) return joinSources(file);
671
- return parser.parse(file, { extname: parseExtName });
672
- }
673
- async run(files, { parsers, mode = "sequential", extension, onStart, onEnd, onUpdate } = {}) {
674
- await onStart?.(files);
675
- const total = files.length;
676
- let processed = 0;
677
- const processOne = async (file) => {
678
- const source = await this.parse(file, {
679
- extension,
680
- parsers
681
- });
682
- const currentProcessed = ++processed;
683
- const percentage = currentProcessed / total * 100;
684
- await onUpdate?.({
685
- file,
686
- source,
687
- processed: currentProcessed,
688
- percentage,
689
- total
690
- });
691
- };
692
- if (mode === "sequential") for (const file of files) await processOne(file);
693
- else await Promise.all(files.map((file) => this.#limit(() => processOne(file))));
694
- await onEnd?.(files);
695
- return files;
696
- }
697
- };
698
- //#endregion
699
- //#region src/createStorage.ts
180
+ //#region src/createAdapter.ts
700
181
  /**
701
- * Factory for implementing custom storage backends that control where generated files are written.
702
- *
703
- * Takes a builder function `(options: TOptions) => Storage` and returns a factory `(options?: TOptions) => Storage`.
704
- * Kubb provides filesystem and in-memory implementations out of the box.
182
+ * Defines a custom adapter that translates a spec format into Kubb's universal
183
+ * AST. Use this when you need to consume GraphQL, gRPC, AsyncAPI, or another
184
+ * domain-specific schema. Built-in adapters: `@kubb/adapter-oas` for
185
+ * OpenAPI/Swagger documents.
705
186
  *
706
- * @note Call the returned factory with optional options to instantiate the storage adapter.
187
+ * Adapters must return an `InputNode` from `parse`. That node is what every
188
+ * plugin in the build consumes.
707
189
  *
708
190
  * @example
709
191
  * ```ts
710
- * import { createStorage } from '@kubb/core'
192
+ * import { createAdapter, ast, type AdapterFactoryOptions } from '@kubb/core'
711
193
  *
712
- * export const memoryStorage = createStorage(() => {
713
- * const store = new Map<string, string>()
714
- * return {
715
- * name: 'memory',
716
- * async hasItem(key) { return store.has(key) },
717
- * async getItem(key) { return store.get(key) ?? null },
718
- * async setItem(key, value) { store.set(key, value) },
719
- * async removeItem(key) { store.delete(key) },
720
- * async getKeys(base) {
721
- * const keys = [...store.keys()]
722
- * return base ? keys.filter((k) => k.startsWith(base)) : keys
723
- * },
724
- * async clear(base) { if (!base) store.clear() },
725
- * }
726
- * })
194
+ * type MyAdapter = AdapterFactoryOptions<'my-adapter', { validate?: boolean }>
727
195
  *
728
- * // Instantiate:
729
- * const storage = memoryStorage()
196
+ * export const myAdapter = createAdapter<MyAdapter>((options) => ({
197
+ * name: 'my-adapter',
198
+ * options,
199
+ * document: null,
200
+ * async parse(_source) {
201
+ * // Convert `source` (path or inline data) into an InputNode.
202
+ * return ast.createInput()
203
+ * },
204
+ * getImports: () => [],
205
+ * async validate() {
206
+ * // Throw or call ctx.error here when the spec is invalid.
207
+ * },
208
+ * }))
730
209
  * ```
731
210
  */
732
- function createStorage(build) {
211
+ function createAdapter(build) {
733
212
  return (options) => build(options ?? {});
734
213
  }
735
214
  //#endregion
736
215
  //#region src/storages/fsStorage.ts
737
216
  /**
738
- * Detects the filesystem error used to indicate that a path does not exist.
739
- */
740
- function isMissingPathError(error) {
741
- return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
742
- }
743
- /**
744
217
  * Built-in filesystem storage driver.
745
218
  *
746
219
  * This is the default storage when no `storage` option is configured in the root config.
@@ -765,23 +238,21 @@ function isMissingPathError(error) {
765
238
  * })
766
239
  * ```
767
240
  */
768
- const fsStorage = createStorage(() => ({
241
+ const fsStorage = require_memoryStorage.createStorage(() => ({
769
242
  name: "fs",
770
243
  async hasItem(key) {
771
244
  try {
772
245
  await (0, node_fs_promises.access)((0, node_path.resolve)(key));
773
246
  return true;
774
- } catch (error) {
775
- if (isMissingPathError(error)) return false;
776
- throw new Error(`Failed to access storage item "${key}"`, { cause: error });
247
+ } catch (_error) {
248
+ return false;
777
249
  }
778
250
  },
779
251
  async getItem(key) {
780
252
  try {
781
253
  return await (0, node_fs_promises.readFile)((0, node_path.resolve)(key), "utf8");
782
- } catch (error) {
783
- if (isMissingPathError(error)) return null;
784
- throw new Error(`Failed to read storage item "${key}"`, { cause: error });
254
+ } catch (_error) {
255
+ return null;
785
256
  }
786
257
  },
787
258
  async setItem(key, value) {
@@ -791,23 +262,22 @@ const fsStorage = createStorage(() => ({
791
262
  await (0, node_fs_promises.rm)((0, node_path.resolve)(key), { force: true });
792
263
  },
793
264
  async getKeys(base) {
794
- const keys = [];
795
265
  const resolvedBase = (0, node_path.resolve)(base ?? process.cwd());
796
- async function walk(dir, prefix) {
266
+ async function* walk(dir, prefix) {
797
267
  let entries;
798
268
  try {
799
269
  entries = await (0, node_fs_promises.readdir)(dir, { withFileTypes: true });
800
- } catch (error) {
801
- if (isMissingPathError(error)) return;
802
- throw new Error(`Failed to list storage keys under "${resolvedBase}"`, { cause: error });
270
+ } catch (_error) {
271
+ return;
803
272
  }
804
273
  for (const entry of entries) {
805
274
  const rel = prefix ? `${prefix}/${entry.name}` : entry.name;
806
- if (entry.isDirectory()) await walk((0, node_path.join)(dir, entry.name), rel);
807
- else keys.push(rel);
275
+ if (entry.isDirectory()) yield* walk((0, node_path.join)(dir, entry.name), rel);
276
+ else yield rel;
808
277
  }
809
278
  }
810
- await walk(resolvedBase, "");
279
+ const keys = [];
280
+ for await (const key of walk(resolvedBase, "")) keys.push(key);
811
281
  return keys;
812
282
  },
813
283
  async clear(base) {
@@ -816,476 +286,637 @@ const fsStorage = createStorage(() => ({
816
286
  }
817
287
  }));
818
288
  //#endregion
819
- //#region package.json
820
- var version = "5.0.0-beta.4";
821
- //#endregion
822
- //#region src/utils/diagnostics.ts
289
+ //#region src/createKubb.ts
823
290
  /**
824
- * Returns a snapshot of the current runtime environment.
825
- *
826
- * Useful for attaching context to debug logs and error reports so that
827
- * issues can be reproduced without manual information gathering.
291
+ * Builds a `Storage` view scoped to the file paths produced by the current build.
292
+ * Reads delegate to the underlying `storage` so source bytes stay where they were
293
+ * written. Writes register the key so subsequent reads and `getKeys` are scoped
294
+ * to this build's output.
828
295
  */
829
- function getDiagnosticInfo() {
830
- return {
831
- nodeVersion: node_process.version,
832
- KubbVersion: version,
833
- platform: process.platform,
834
- arch: process.arch,
835
- cwd: process.cwd()
836
- };
837
- }
838
- //#endregion
839
- //#region src/utils/isInputPath.ts
840
- function isInputPath(config) {
841
- return typeof config?.input === "object" && config.input !== null && "path" in config.input;
842
- }
843
- //#endregion
844
- //#region src/createKubb.ts
845
- async function setup(userConfig, options = {}) {
846
- const hooks = options.hooks ?? new AsyncEventEmitter();
847
- const sources = /* @__PURE__ */ new Map();
848
- const diagnosticInfo = getDiagnosticInfo();
849
- if (Array.isArray(userConfig.input)) await hooks.emit("kubb:warn", { message: "This feature is still under development — use with caution" });
850
- await hooks.emit("kubb:debug", {
851
- date: /* @__PURE__ */ new Date(),
852
- logs: [
853
- "Configuration:",
854
- ` • Name: ${userConfig.name || "unnamed"}`,
855
- ` • Root: ${userConfig.root || process.cwd()}`,
856
- ` • Output: ${userConfig.output?.path || "not specified"}`,
857
- ` • Plugins: ${userConfig.plugins?.length || 0}`,
858
- "Output Settings:",
859
- ` • Storage: ${userConfig.storage ? `custom(${userConfig.storage.name})` : userConfig.output?.write === false ? "disabled" : "filesystem (default)"}`,
860
- ` • Formatter: ${userConfig.output?.format || "none"}`,
861
- ` • Linter: ${userConfig.output?.lint || "none"}`,
862
- "Environment:",
863
- Object.entries(diagnosticInfo).map(([key, value]) => ` • ${key}: ${value}`).join("\n")
864
- ]
865
- });
866
- try {
867
- if (isInputPath(userConfig) && !new URLPath(userConfig.input.path).isURL) {
868
- await exists(userConfig.input.path);
869
- await hooks.emit("kubb:debug", {
870
- date: /* @__PURE__ */ new Date(),
871
- logs: [`✓ Input file validated: ${userConfig.input.path}`]
872
- });
873
- }
874
- } catch (caughtError) {
875
- if (isInputPath(userConfig)) {
876
- const error = caughtError;
877
- throw new Error(`Cannot read file/URL defined in \`input.path\` or set with \`kubb generate PATH\` in the CLI of your Kubb config ${userConfig.input.path}`, { cause: error });
296
+ function createSourcesView(storage) {
297
+ const paths = /* @__PURE__ */ new Set();
298
+ return require_memoryStorage.createStorage(() => ({
299
+ name: `${storage.name}:sources`,
300
+ async hasItem(key) {
301
+ return paths.has(key) && await storage.hasItem(key);
302
+ },
303
+ async getItem(key) {
304
+ return paths.has(key) ? storage.getItem(key) : null;
305
+ },
306
+ async setItem(key, value) {
307
+ paths.add(key);
308
+ await storage.setItem(key, value);
309
+ },
310
+ async removeItem(key) {
311
+ paths.delete(key);
312
+ await storage.removeItem(key);
313
+ },
314
+ async getKeys(base) {
315
+ if (!base) return [...paths];
316
+ const result = [];
317
+ for (const key of paths) if (key.startsWith(base)) result.push(key);
318
+ return result;
319
+ },
320
+ async clear() {
321
+ paths.clear();
322
+ await storage.clear();
878
323
  }
879
- }
880
- const config = {
324
+ }))();
325
+ }
326
+ function resolveConfig(userConfig) {
327
+ return {
881
328
  ...userConfig,
882
329
  root: userConfig.root || process.cwd(),
883
330
  parsers: userConfig.parsers ?? [],
884
- adapter: userConfig.adapter,
885
331
  output: {
886
332
  format: false,
887
333
  lint: false,
888
- write: true,
889
- extension: require_PluginDriver.DEFAULT_EXTENSION,
890
- defaultBanner: require_PluginDriver.DEFAULT_BANNER,
334
+ extension: { ".ts": ".ts" },
335
+ defaultBanner: "simple",
891
336
  ...userConfig.output
892
337
  },
893
- devtools: userConfig.devtools ? {
894
- studioUrl: require_PluginDriver.DEFAULT_STUDIO_URL,
895
- ...typeof userConfig.devtools === "boolean" ? {} : userConfig.devtools
896
- } : void 0,
897
- plugins: userConfig.plugins
898
- };
899
- const storage = config.output.write === false ? null : config.storage ?? fsStorage();
900
- if (config.output.clean) {
901
- await hooks.emit("kubb:debug", {
902
- date: /* @__PURE__ */ new Date(),
903
- logs: ["Cleaning output directories", ` • Output: ${config.output.path}`]
904
- });
905
- await storage?.clear((0, node_path.resolve)(config.root, config.output.path));
906
- }
907
- const driver = new require_PluginDriver.PluginDriver(config, { hooks });
908
- function registerMiddlewareHook(event, middlewareHooks) {
909
- const handler = middlewareHooks[event];
910
- if (handler) hooks.on(event, handler);
911
- }
912
- for (const middleware of config.middleware ?? []) for (const event of Object.keys(middleware.hooks)) registerMiddlewareHook(event, middleware.hooks);
913
- if (config.adapter) {
914
- const source = inputToAdapterSource(config);
915
- await hooks.emit("kubb:debug", {
916
- date: /* @__PURE__ */ new Date(),
917
- logs: [`Running adapter: ${config.adapter.name}`]
918
- });
919
- driver.adapter = config.adapter;
920
- driver.inputNode = await config.adapter.parse(source);
921
- await hooks.emit("kubb:debug", {
922
- date: /* @__PURE__ */ new Date(),
923
- logs: [
924
- `✓ Adapter '${config.adapter.name}' resolved InputNode`,
925
- ` • Schemas: ${driver.inputNode.schemas.length}`,
926
- ` • Operations: ${driver.inputNode.operations.length}`
927
- ]
928
- });
929
- }
930
- return {
931
- config,
932
- hooks,
933
- driver,
934
- sources,
935
- storage
338
+ storage: userConfig.storage ?? fsStorage(),
339
+ reporters: userConfig.reporters ?? [],
340
+ plugins: userConfig.plugins ?? []
936
341
  };
937
342
  }
938
343
  /**
939
- * Walks the AST and dispatches nodes to a plugin's direct AST hooks
940
- * (`schema`, `operation`, `operations`).
344
+ * Kubb code-generation instance bound to a single config entry. Resolves the user
345
+ * config during `setup()` and shares `hooks`, `storage`, `driver`, and `config` across
346
+ * the `setup → build` lifecycle.
941
347
  *
942
- * When `include` contains only operation-scoped filters (`tag`, `operationId`, `path`,
943
- * `method`, `contentType`) and no `schemaName` filter, the function pre-computes the set
944
- * of top-level schema names transitively reachable from the included operations and skips
945
- * schemas that fall outside that set. This ensures that component schemas referenced
946
- * exclusively by excluded operations are not generated.
348
+ * Attach event listeners to `.hooks` before calling `setup()` or `build()`.
349
+ *
350
+ * @example
351
+ * ```ts
352
+ * const kubb = createKubb(userConfig)
353
+ * kubb.hooks.on('kubb:plugin:end', ({ plugin, duration }) => console.log(plugin.name, duration))
354
+ * const { files, diagnostics } = await kubb.safeBuild()
355
+ * ```
947
356
  */
948
- async function runPluginAstHooks(plugin, context) {
949
- const { adapter, inputNode, resolver, driver } = context;
950
- const { exclude, include, override } = plugin.options;
951
- if (!adapter || !inputNode) throw new Error(`[${plugin.name}] No adapter found. Add an OAS adapter (e.g. pluginOas()) before this plugin in your Kubb config.`);
952
- function resolveRenderer(gen) {
953
- return gen.renderer === null ? void 0 : gen.renderer ?? plugin.renderer ?? context.config.renderer;
357
+ var Kubb = class {
358
+ hooks;
359
+ #userConfig;
360
+ #config = null;
361
+ #driver = null;
362
+ #storage = null;
363
+ constructor(userConfig, options = {}) {
364
+ this.#userConfig = userConfig;
365
+ this.hooks = options.hooks ?? new require_memoryStorage.AsyncEventEmitter();
954
366
  }
955
- const generators = plugin.generators ?? [];
956
- const collectedOperations = [];
957
- const generatorContext = {
958
- ...context,
959
- resolver: driver.getResolver(plugin.name)
960
- };
961
- const operationFilterTypes = new Set([
962
- "tag",
963
- "operationId",
964
- "path",
965
- "method",
966
- "contentType"
967
- ]);
968
- const hasOperationBasedIncludes = include?.some(({ type }) => operationFilterTypes.has(type)) ?? false;
969
- const hasSchemaNameIncludes = include?.some(({ type }) => type === "schemaName") ?? false;
970
- let allowedSchemaNames;
971
- if (hasOperationBasedIncludes && !hasSchemaNameIncludes) allowedSchemaNames = (0, _kubb_ast.collectUsedSchemaNames)(inputNode.operations.filter((op) => resolver.resolveOptions(op, {
972
- options: plugin.options,
973
- exclude,
974
- include,
975
- override
976
- }) !== null), inputNode.schemas);
977
- await (0, _kubb_ast.walk)(inputNode, {
978
- depth: "shallow",
979
- async schema(node) {
980
- const transformedNode = plugin.transformer ? (0, _kubb_ast.transform)(node, plugin.transformer) : node;
981
- if (allowedSchemaNames !== void 0 && transformedNode.name && !allowedSchemaNames.has(transformedNode.name)) return;
982
- const options = resolver.resolveOptions(transformedNode, {
983
- options: plugin.options,
984
- exclude,
985
- include,
986
- override
987
- });
988
- if (options === null) return;
989
- const ctx = {
990
- ...generatorContext,
991
- options
992
- };
993
- for (const gen of generators) {
994
- if (!gen.schema) continue;
995
- await require_PluginDriver.applyHookResult(await gen.schema(transformedNode, ctx), driver, resolveRenderer(gen));
996
- }
997
- await driver.hooks.emit("kubb:generate:schema", transformedNode, ctx);
998
- },
999
- async operation(node) {
1000
- const transformedNode = plugin.transformer ? (0, _kubb_ast.transform)(node, plugin.transformer) : node;
1001
- const options = resolver.resolveOptions(transformedNode, {
1002
- options: plugin.options,
1003
- exclude,
1004
- include,
1005
- override
1006
- });
1007
- if (options !== null) {
1008
- collectedOperations.push(transformedNode);
1009
- const ctx = {
1010
- ...generatorContext,
1011
- options
1012
- };
1013
- for (const gen of generators) {
1014
- if (!gen.operation) continue;
1015
- await require_PluginDriver.applyHookResult(await gen.operation(transformedNode, ctx), driver, resolveRenderer(gen));
1016
- }
1017
- await driver.hooks.emit("kubb:generate:operation", transformedNode, ctx);
1018
- }
1019
- }
1020
- });
1021
- if (collectedOperations.length > 0) {
1022
- const ctx = {
1023
- ...generatorContext,
1024
- options: plugin.options
1025
- };
1026
- for (const gen of generators) {
1027
- if (!gen.operations) continue;
1028
- await require_PluginDriver.applyHookResult(await gen.operations(collectedOperations, ctx), driver, resolveRenderer(gen));
367
+ get storage() {
368
+ if (!this.#storage) throw new Error("[kubb] setup() must be called before accessing storage");
369
+ return this.#storage;
370
+ }
371
+ get driver() {
372
+ if (!this.#driver) throw new Error("[kubb] setup() must be called before accessing driver");
373
+ return this.#driver;
374
+ }
375
+ get config() {
376
+ if (!this.#config) throw new Error("[kubb] setup() must be called before accessing config");
377
+ return this.#config;
378
+ }
379
+ /**
380
+ * Resolves config and initializes the driver. `build()` calls this automatically.
381
+ */
382
+ async setup() {
383
+ const config = resolveConfig(this.#userConfig);
384
+ const driver = new require_memoryStorage.KubbDriver(config, { hooks: this.hooks });
385
+ const storage = createSourcesView(config.storage);
386
+ this.hooks.setMaxListeners(Math.max(10, config.plugins.length * 4));
387
+ if (config.output.clean) await config.storage.clear((0, node_path.resolve)(config.root, config.output.path));
388
+ await driver.setup();
389
+ this.#config = config;
390
+ this.#driver = driver;
391
+ this.#storage = storage;
392
+ }
393
+ /**
394
+ * Runs the full pipeline and throws on any plugin error.
395
+ * Automatically calls `setup()` if needed.
396
+ */
397
+ async build() {
398
+ const out = await this.safeBuild();
399
+ if (require_memoryStorage.Diagnostics.hasError(out.diagnostics)) {
400
+ const errors = out.diagnostics.filter(require_memoryStorage.Diagnostics.isProblem).filter((diagnostic) => diagnostic.severity === "error").map((diagnostic) => diagnostic.cause ?? new require_memoryStorage.Diagnostics.Error(diagnostic));
401
+ throw new require_memoryStorage.BuildError(`Build failed with ${errors.length} ${errors.length === 1 ? "error" : "errors"}`, { errors });
1029
402
  }
1030
- await driver.hooks.emit("kubb:generate:operations", collectedOperations, ctx);
403
+ return out;
1031
404
  }
1032
- }
1033
- async function safeBuild(setupResult) {
1034
- const { driver, hooks, sources, storage } = setupResult;
1035
- const failedPlugins = /* @__PURE__ */ new Set();
1036
- const pluginTimings = /* @__PURE__ */ new Map();
1037
- const config = driver.config;
1038
- try {
1039
- await driver.emitSetupHooks();
1040
- if (driver.adapter && driver.inputNode) await hooks.emit("kubb:build:start", {
1041
- config,
1042
- adapter: driver.adapter,
1043
- inputNode: driver.inputNode,
1044
- getPlugin: driver.getPlugin.bind(driver),
1045
- get files() {
1046
- return driver.fileManager.files;
1047
- },
1048
- upsertFile: (...files) => driver.fileManager.upsert(...files)
1049
- });
1050
- for (const plugin of driver.plugins.values()) {
1051
- const context = driver.getContext(plugin);
1052
- const hrStart = process.hrtime();
1053
- try {
1054
- const timestamp = /* @__PURE__ */ new Date();
1055
- await hooks.emit("kubb:plugin:start", { plugin });
1056
- await hooks.emit("kubb:debug", {
1057
- date: timestamp,
1058
- logs: ["Starting plugin...", ` • Plugin Name: ${plugin.name}`]
1059
- });
1060
- if (plugin.generators?.length || driver.hasRegisteredGenerators(plugin.name)) await runPluginAstHooks(plugin, context);
1061
- const duration = getElapsedMs(hrStart);
1062
- pluginTimings.set(plugin.name, duration);
1063
- await hooks.emit("kubb:plugin:end", {
1064
- plugin,
1065
- duration,
1066
- success: true,
1067
- config,
1068
- get files() {
1069
- return driver.fileManager.files;
1070
- },
1071
- upsertFile: (...files) => driver.fileManager.upsert(...files)
1072
- });
1073
- await hooks.emit("kubb:debug", {
1074
- date: /* @__PURE__ */ new Date(),
1075
- logs: [`✓ Plugin started successfully (${formatMs(duration)})`]
1076
- });
1077
- } catch (caughtError) {
1078
- const error = caughtError;
1079
- const errorTimestamp = /* @__PURE__ */ new Date();
1080
- const duration = getElapsedMs(hrStart);
1081
- await hooks.emit("kubb:plugin:end", {
1082
- plugin,
1083
- duration,
1084
- success: false,
1085
- error,
1086
- config,
1087
- get files() {
1088
- return driver.fileManager.files;
1089
- },
1090
- upsertFile: (...files) => driver.fileManager.upsert(...files)
1091
- });
1092
- await hooks.emit("kubb:debug", {
1093
- date: errorTimestamp,
1094
- logs: [
1095
- "✗ Plugin start failed",
1096
- ` • Plugin Name: ${plugin.name}`,
1097
- ` • Error: ${error.constructor.name} - ${error.message}`,
1098
- " • Stack Trace:",
1099
- error.stack || "No stack trace available"
1100
- ]
1101
- });
1102
- failedPlugins.add({
1103
- plugin,
1104
- error
1105
- });
1106
- }
405
+ /**
406
+ * Runs the full pipeline and captures errors in `BuildOutput` instead of throwing.
407
+ * Automatically calls `setup()` if needed.
408
+ */
409
+ async safeBuild() {
410
+ try {
411
+ var _usingCtx$1 = require_memoryStorage._usingCtx();
412
+ if (!this.#driver) await this.setup();
413
+ const cleanup = _usingCtx$1.u(this);
414
+ const driver = cleanup.driver;
415
+ const storage = cleanup.storage;
416
+ const { diagnostics } = await driver.run({ storage });
417
+ return {
418
+ diagnostics,
419
+ files: driver.fileManager.files,
420
+ driver,
421
+ storage
422
+ };
423
+ } catch (_) {
424
+ _usingCtx$1.e = _;
425
+ } finally {
426
+ _usingCtx$1.d();
1107
427
  }
1108
- await hooks.emit("kubb:plugins:end", {
1109
- config,
1110
- get files() {
1111
- return driver.fileManager.files;
1112
- },
1113
- upsertFile: (...files) => driver.fileManager.upsert(...files)
1114
- });
1115
- const files = driver.fileManager.files;
1116
- const parsersMap = /* @__PURE__ */ new Map();
1117
- for (const parser of config.parsers) if (parser.extNames) for (const extname of parser.extNames) parsersMap.set(extname, parser);
1118
- const fileProcessor = new FileProcessor();
1119
- await hooks.emit("kubb:debug", {
1120
- date: /* @__PURE__ */ new Date(),
1121
- logs: [`Writing ${files.length} files...`]
1122
- });
1123
- await fileProcessor.run(files, {
1124
- parsers: parsersMap,
1125
- extension: config.output.extension,
1126
- onStart: async (processingFiles) => {
1127
- await hooks.emit("kubb:files:processing:start", { files: processingFiles });
1128
- },
1129
- onUpdate: async ({ file, source, processed, total, percentage }) => {
1130
- await hooks.emit("kubb:file:processing:update", {
1131
- file,
1132
- source,
1133
- processed,
1134
- total,
1135
- percentage,
1136
- config
1137
- });
1138
- if (source) {
1139
- await storage?.setItem(file.path, source);
1140
- sources.set(file.path, source);
1141
- }
1142
- },
1143
- onEnd: async (processedFiles) => {
1144
- await hooks.emit("kubb:files:processing:end", { files: processedFiles });
1145
- await hooks.emit("kubb:debug", {
1146
- date: /* @__PURE__ */ new Date(),
1147
- logs: [`✓ File write process completed for ${processedFiles.length} files`]
1148
- });
1149
- }
1150
- });
1151
- await hooks.emit("kubb:build:end", {
1152
- files,
1153
- config,
1154
- outputDir: (0, node_path.resolve)(config.root, config.output.path)
1155
- });
1156
- return {
1157
- failedPlugins,
1158
- files,
1159
- driver,
1160
- pluginTimings,
1161
- sources
1162
- };
1163
- } catch (error) {
1164
- return {
1165
- failedPlugins,
1166
- files: [],
1167
- driver,
1168
- pluginTimings,
1169
- error,
1170
- sources
1171
- };
1172
- } finally {
1173
- driver.dispose();
1174
428
  }
1175
- }
1176
- async function build(setupResult) {
1177
- const { files, driver, failedPlugins, pluginTimings, error, sources } = await safeBuild(setupResult);
1178
- if (error) throw error;
1179
- if (failedPlugins.size > 0) {
1180
- const errors = [...failedPlugins].map(({ error }) => error);
1181
- throw new BuildError(`Build Error with ${failedPlugins.size} failed plugins`, { errors });
429
+ dispose() {
430
+ this.#driver?.dispose();
1182
431
  }
1183
- return {
1184
- failedPlugins,
1185
- files,
1186
- driver,
1187
- pluginTimings,
1188
- error: void 0,
1189
- sources
1190
- };
432
+ [Symbol.dispose]() {
433
+ this.dispose();
434
+ }
435
+ };
436
+ /**
437
+ * Constructs a {@link Kubb} build orchestrator from a user config. Equivalent
438
+ * to `new Kubb(userConfig, options)` and the canonical public entry point.
439
+ *
440
+ * @example
441
+ * ```ts
442
+ * import { createKubb } from '@kubb/core'
443
+ * import { adapterOas } from '@kubb/adapter-oas'
444
+ * import { pluginTs } from '@kubb/plugin-ts'
445
+ *
446
+ * const kubb = createKubb({
447
+ * input: { path: './petStore.yaml' },
448
+ * output: { path: './src/gen' },
449
+ * adapter: adapterOas(),
450
+ * plugins: [pluginTs()],
451
+ * })
452
+ *
453
+ * await kubb.build()
454
+ * ```
455
+ */
456
+ function createKubb(userConfig, options = {}) {
457
+ return new Kubb(userConfig, options);
1191
458
  }
1192
- function inputToAdapterSource(config) {
1193
- const input = config.input;
1194
- if (!input) throw new Error("[kubb] input is required when using an adapter. Provide input.path or input.data in your config.");
1195
- if (Array.isArray(input)) return {
1196
- type: "paths",
1197
- paths: input.map((i) => new URLPath(i.path).isURL ? i.path : (0, node_path.resolve)(config.root, i.path))
1198
- };
1199
- if ("data" in input) return {
1200
- type: "data",
1201
- data: input.data
1202
- };
1203
- if (new URLPath(input.path).isURL) return {
1204
- type: "path",
1205
- path: input.path
459
+ //#endregion
460
+ //#region src/createReporter.ts
461
+ /**
462
+ * Defines a reporter. When the definition has a `drain`, the returned reporter buffers each value
463
+ * `report` returns and hands the array to `drain` once, then clears it. Without a `drain`, nothing
464
+ * is buffered. Wiring the reporter onto the run's events is the host's job, so the reporter only
465
+ * ever deals with a {@link GenerationResult}.
466
+ *
467
+ * @example
468
+ * ```ts
469
+ * import { createReporter, Diagnostics } from '@kubb/core'
470
+ *
471
+ * export const jsonReporter = createReporter({
472
+ * name: 'json',
473
+ * report(result) {
474
+ * return { status: Diagnostics.hasError(result.diagnostics) ? 'failed' : 'success', diagnostics: result.diagnostics }
475
+ * },
476
+ * drain(context, reports) {
477
+ * process.stdout.write(`${JSON.stringify(reports, null, 2)}\n`)
478
+ * },
479
+ * })
480
+ * ```
481
+ */
482
+ function createReporter(reporter) {
483
+ const drain = reporter.drain;
484
+ if (!drain) return {
485
+ name: reporter.name,
486
+ async report(result, context) {
487
+ await reporter.report(result, context);
488
+ }
1206
489
  };
490
+ const reports = [];
1207
491
  return {
1208
- type: "path",
1209
- path: (0, node_path.resolve)(config.root, input.path)
492
+ name: reporter.name,
493
+ async report(result, context) {
494
+ reports.push(await reporter.report(result, context));
495
+ },
496
+ async drain(context) {
497
+ await drain(context, reports);
498
+ reports.length = 0;
499
+ }
1210
500
  };
1211
501
  }
1212
502
  /**
1213
- * Creates a Kubb instance bound to a single config entry.
503
+ * Picks the reporters whose `name` matches one of `names`, in the order the names are given.
504
+ * The config carries every available reporter, and the host selects which to activate by name
505
+ * (the CLI maps `--reporter` to this). Duplicate names and names without a matching reporter are
506
+ * skipped.
507
+ */
508
+ function selectReporters(reporters, names) {
509
+ const seen = /* @__PURE__ */ new Set();
510
+ const selected = [];
511
+ for (const name of names) {
512
+ if (seen.has(name)) continue;
513
+ seen.add(name);
514
+ const reporter = reporters.find((candidate) => candidate.name === name);
515
+ if (reporter) selected.push(reporter);
516
+ }
517
+ return selected;
518
+ }
519
+ //#endregion
520
+ //#region src/defineLogger.ts
521
+ /**
522
+ * Numeric log-level thresholds used internally to compare verbosity.
1214
523
  *
1215
- * Accepts a user-facing config shape and resolves it to a full {@link Config} during
1216
- * `setup()`. The instance then holds shared state (`hooks`, `sources`, `driver`, `config`)
1217
- * across the `setup → build` lifecycle. Attach event listeners to `kubb.hooks` before
1218
- * calling `setup()` or `build()`.
524
+ * Higher numbers are more verbose.
525
+ */
526
+ const logLevel = {
527
+ silent: Number.NEGATIVE_INFINITY,
528
+ error: 0,
529
+ warn: 1,
530
+ info: 3,
531
+ verbose: 4
532
+ };
533
+ /**
534
+ * Defines a typed logger. The `install` method subscribes to lifecycle events
535
+ * on the shared emitter and forwards them to the logger's destination.
1219
536
  *
1220
537
  * @example
1221
538
  * ```ts
1222
- * const kubb = createKubb(userConfig)
539
+ * import { defineLogger } from '@kubb/core'
1223
540
  *
1224
- * kubb.hooks.on('kubb:plugin:end', ({ plugin, duration }) => {
1225
- * console.log(`${plugin.name} completed in ${duration}ms`)
541
+ * export const myLogger = defineLogger({
542
+ * name: 'my-logger',
543
+ * install(context) {
544
+ * context.on('kubb:info', ({ message }) => console.log('ℹ', message))
545
+ * context.on('kubb:error', ({ error }) => console.error('✗', error.message))
546
+ * },
1226
547
  * })
1227
- *
1228
- * const { files, failedPlugins } = await kubb.safeBuild()
1229
548
  * ```
1230
549
  */
1231
- function createKubb(userConfig, options = {}) {
1232
- const hooks = options.hooks ?? new AsyncEventEmitter();
1233
- let setupResult;
1234
- const instance = {
1235
- get hooks() {
1236
- return hooks;
1237
- },
1238
- get sources() {
1239
- return setupResult?.sources ?? /* @__PURE__ */ new Map();
1240
- },
1241
- get driver() {
1242
- return setupResult?.driver;
1243
- },
1244
- get config() {
1245
- return setupResult?.config;
1246
- },
1247
- async setup() {
1248
- setupResult = await setup(userConfig, { hooks });
1249
- },
1250
- async build() {
1251
- if (!setupResult) await instance.setup();
1252
- return build(setupResult);
550
+ function defineLogger(logger) {
551
+ return logger;
552
+ }
553
+ //#endregion
554
+ //#region src/reporters/report.ts
555
+ /**
556
+ * Builds the normalized {@link Report} for one config from its {@link GenerationResult}. Splits the
557
+ * diagnostics into problems and per-plugin timings (slowest first) and derives the plugin and issue
558
+ * counts, so every reporter renders the same data.
559
+ */
560
+ function buildReport(result) {
561
+ const { config, diagnostics, filesCreated, status, hrStart } = result;
562
+ const failed = require_memoryStorage.Diagnostics.failedPlugins(diagnostics);
563
+ const total = config.plugins?.length ?? 0;
564
+ const counts = require_memoryStorage.Diagnostics.count(diagnostics);
565
+ const problems = diagnostics.filter(require_memoryStorage.Diagnostics.isProblem);
566
+ const timings = diagnostics.filter(require_memoryStorage.Diagnostics.isPerformance).sort((a, b) => b.duration - a.duration).map((diagnostic) => ({
567
+ plugin: diagnostic.plugin,
568
+ durationMs: diagnostic.duration
569
+ }));
570
+ return {
571
+ name: config.name ?? "",
572
+ status,
573
+ plugins: {
574
+ passed: total - failed.length,
575
+ failed,
576
+ total
1253
577
  },
1254
- async safeBuild() {
1255
- if (!setupResult) await instance.setup();
1256
- return safeBuild(setupResult);
1257
- }
578
+ counts,
579
+ filesCreated,
580
+ durationMs: require_memoryStorage.getElapsedMs(hrStart),
581
+ output: (0, node_path.resolve)(config.root, config.output.path),
582
+ timings,
583
+ diagnostics: problems.map((diagnostic) => require_memoryStorage.Diagnostics.serialize(diagnostic))
1258
584
  };
1259
- return instance;
1260
585
  }
1261
586
  //#endregion
587
+ //#region src/reporters/cliReporter.ts
588
+ /**
589
+ * Builds the vitest/jest-style summary for one {@link Report}: right-aligned dim labels with
590
+ * `N passed (total)` counts, and a per-plugin `Timings` section when `showTimings`.
591
+ */
592
+ function buildSummaryLines(report, { showTimings }) {
593
+ const { status, plugins, counts, filesCreated, durationMs, output, timings } = report;
594
+ const rows = [];
595
+ rows.push(["Plugins", status === "success" ? `${(0, node_util.styleText)("green", `${plugins.passed} passed`)} (${plugins.total})` : `${(0, node_util.styleText)("green", `${plugins.passed} passed`)} | ${(0, node_util.styleText)("red", `${plugins.failed.length} failed`)} (${plugins.total})`]);
596
+ if (status === "failed" && plugins.failed.length > 0) rows.push(["Failed", plugins.failed.map((name) => randomCliColor(name)).join(", ")]);
597
+ if (counts.errors > 0 || counts.warnings > 0) {
598
+ const issues = [counts.errors > 0 ? (0, node_util.styleText)("red", `${counts.errors} ${counts.errors === 1 ? "error" : "errors"}`) : void 0, counts.warnings > 0 ? (0, node_util.styleText)("yellow", `${counts.warnings} ${counts.warnings === 1 ? "warning" : "warnings"}`) : void 0].filter(Boolean).join(" | ");
599
+ rows.push(["Issues", issues]);
600
+ }
601
+ rows.push(["Files", `${(0, node_util.styleText)("green", String(filesCreated))} generated`]);
602
+ rows.push(["Duration", (0, node_util.styleText)("green", require_memoryStorage.formatMs(durationMs))]);
603
+ rows.push(["Output", output]);
604
+ const labelWidth = Math.max(...rows.map(([label]) => label.length), timings.length > 0 ? 7 : 0);
605
+ const lines = rows.map(([label, value]) => `${(0, node_util.styleText)("dim", label.padStart(labelWidth))} ${value}`);
606
+ if (showTimings && timings.length > 0) {
607
+ const nameWidth = Math.max(0, ...timings.map((timing) => timing.plugin.length));
608
+ const indent = " ".repeat(labelWidth + 2);
609
+ lines.push((0, node_util.styleText)("dim", "Timings".padStart(labelWidth)));
610
+ for (const timing of timings) {
611
+ const timeStr = require_memoryStorage.formatMs(timing.durationMs);
612
+ const barLength = Math.min(Math.ceil(timing.durationMs / 100), 10);
613
+ const bar = (0, node_util.styleText)("dim", "█".repeat(barLength));
614
+ lines.push(`${indent}${(0, node_util.styleText)("dim", "•")} ${timing.plugin.padEnd(nameWidth)} ${bar} ${timeStr}`);
615
+ }
616
+ }
617
+ return lines;
618
+ }
619
+ /**
620
+ * Renders the summary as plain `console.log` lines so it works in every CLI (no clack/TTY
621
+ * dependency): a blank line, the config name colored by status, then the summary rows.
622
+ */
623
+ function renderSummary(lines, { title, status }) {
624
+ console.log("");
625
+ if (title) console.log((0, node_util.styleText)(status === "failed" ? "red" : "green", title));
626
+ for (const line of lines) console.log(line);
627
+ }
628
+ /**
629
+ * The default `cli` reporter. Renders the {@link Report} for each config as it finishes, independent
630
+ * of the live logger view. Suppressed at `silent`; the `verbose` level adds the per-plugin timings.
631
+ */
632
+ const cliReporter = createReporter({
633
+ name: "cli",
634
+ report(result, { logLevel: logLevel$1 }) {
635
+ if (logLevel$1 <= logLevel.silent) return;
636
+ const report = buildReport(result);
637
+ renderSummary(buildSummaryLines(report, { showTimings: logLevel$1 >= logLevel.verbose }), {
638
+ title: report.name,
639
+ status: report.status
640
+ });
641
+ }
642
+ });
643
+ //#endregion
644
+ //#region src/reporters/fileReporter.ts
645
+ /**
646
+ * Builds the `## Summary` section: the same counts the cli and json reporters expose, as a list of
647
+ * `label value` rows with the labels padded to a common width.
648
+ */
649
+ function buildSummarySection(report) {
650
+ const { status, plugins, counts, filesCreated, durationMs, output } = report;
651
+ const rows = [["Status", status], ["Plugins", status === "success" ? `${plugins.passed} passed (${plugins.total})` : `${plugins.passed} passed | ${plugins.failed.length} failed (${plugins.total})`]];
652
+ if (plugins.failed.length > 0) rows.push(["Failed", plugins.failed.join(", ")]);
653
+ rows.push(["Issues", `${counts.errors} errors | ${counts.warnings} warnings | ${counts.infos} infos`]);
654
+ rows.push(["Files", `${filesCreated} generated`]);
655
+ rows.push(["Duration", require_memoryStorage.formatMs(durationMs)]);
656
+ rows.push(["Output", output]);
657
+ const labelWidth = Math.max(...rows.map(([label]) => label.length));
658
+ return [
659
+ "## Summary",
660
+ "",
661
+ ...rows.map(([label, value]) => ` ${label.padEnd(labelWidth)} ${value}`)
662
+ ];
663
+ }
664
+ /**
665
+ * Builds the `## Problems` section: each problem rendered in the miette block format, blocks
666
+ * separated by a blank line. Returns an empty array when there are no problems, so the caller
667
+ * can drop the heading.
668
+ */
669
+ function buildProblemSection(diagnostics) {
670
+ const problems = diagnostics.filter(require_memoryStorage.Diagnostics.isProblem);
671
+ if (problems.length === 0) return [];
672
+ return [
673
+ "## Problems",
674
+ "",
675
+ problems.map((diagnostic) => require_memoryStorage.Diagnostics.formatLines(diagnostic).join("\n")).join("\n\n")
676
+ ];
677
+ }
678
+ /**
679
+ * Builds the `## Timings` section from a {@link Report}: one `plugin duration` row per record,
680
+ * slowest first with the plugin names left-aligned and the durations right-aligned. Returns an
681
+ * empty array when there are no timings.
682
+ */
683
+ function buildTimingSection(report) {
684
+ const { timings } = report;
685
+ if (timings.length === 0) return [];
686
+ const nameWidth = Math.max(...timings.map((timing) => timing.plugin.length));
687
+ const durations = timings.map((timing) => require_memoryStorage.formatMs(timing.durationMs));
688
+ const durationWidth = Math.max(...durations.map((duration) => duration.length));
689
+ return [
690
+ "## Timings",
691
+ "",
692
+ ...timings.map((timing, index) => ` ${timing.plugin.padEnd(nameWidth)} ${durations[index].padStart(durationWidth)}`)
693
+ ];
694
+ }
695
+ /**
696
+ * The `file` reporter. Writes a config's {@link Report} to `.kubb/kubb-<name>-<timestamp>.log` as a
697
+ * plain-text document: a `# <name> — <timestamp>` header, a `## Summary` with the same counts the
698
+ * cli and json reporters expose, a `## Problems` section in the miette block format, and a
699
+ * `## Timings` section. Selected with `--reporter file` (or `reporters: ['file']`), replacing the
700
+ * old `--debug` flag.
701
+ *
702
+ * @note Unlike the streaming logger it replaced, it captures the collected diagnostics once a
703
+ * config finishes, not the live `kubb:info`/`kubb:plugin` event stream. Color is stripped so the
704
+ * file stays plain text even when the run is attached to a TTY.
705
+ */
706
+ const fileReporter = createReporter({
707
+ name: "file",
708
+ async report(result) {
709
+ const { diagnostics, config } = result;
710
+ if (diagnostics.length === 0) return;
711
+ const report = buildReport(result);
712
+ const content = (0, node_util.stripVTControlCharacters)([config.name ? `# ${config.name} — ${(/* @__PURE__ */ new Date()).toISOString()}` : `# ${(/* @__PURE__ */ new Date()).toISOString()}`, ...[
713
+ buildSummarySection(report),
714
+ buildProblemSection(diagnostics),
715
+ buildTimingSection(report)
716
+ ].filter((section) => section.length > 0).map((section) => section.join("\n"))].join("\n\n"));
717
+ const baseName = `${[
718
+ "kubb",
719
+ config.name,
720
+ Date.now()
721
+ ].filter(Boolean).join("-")}.log`;
722
+ const pathName = (0, node_path.resolve)(node_process.default.cwd(), ".kubb", baseName);
723
+ await write(pathName, `${content}\n`);
724
+ console.error(`Debug log written to ${(0, node_path.relative)(node_process.default.cwd(), pathName)}`);
725
+ }
726
+ });
727
+ //#endregion
728
+ //#region src/reporters/jsonReporter.ts
729
+ /**
730
+ * The `json` reporter. `report` returns one config's {@link Report}, which {@link createReporter}
731
+ * buffers, and `drain` writes them as a single pretty-printed JSON array on `kubb:lifecycle:end`.
732
+ * Buffering keeps a multi-config run one valid JSON document on stdout instead of concatenated
733
+ * objects that would break `jq .`. The terminal reporter is suppressed while `json` is active so
734
+ * stdout stays valid JSON.
735
+ */
736
+ const jsonReporter = createReporter({
737
+ name: "json",
738
+ report(result) {
739
+ return buildReport(result);
740
+ },
741
+ drain(_context, reports) {
742
+ node_process.default.stdout.write(`${JSON.stringify(reports, null, 2)}\n`);
743
+ }
744
+ });
745
+ //#endregion
746
+ //#region src/Telemetry.ts
747
+ /**
748
+ * Anonymous OTLP usage telemetry for the Kubb run. All methods are static, so call them as
749
+ * `Telemetry.build(...)`, `Telemetry.send(...)`, and `Telemetry.isDisabled()`. No file paths,
750
+ * OpenAPI specs, or secrets are ever included, and sending fails silently to never break a run.
751
+ */
752
+ var Telemetry = class Telemetry {
753
+ /**
754
+ * Returns `true` when telemetry is disabled via `DO_NOT_TRACK` or `KUBB_DISABLE_TELEMETRY`.
755
+ */
756
+ static isDisabled() {
757
+ return node_process.default.env["DO_NOT_TRACK"] === "1" || node_process.default.env["DO_NOT_TRACK"] === "true" || node_process.default.env["KUBB_DISABLE_TELEMETRY"] === "1" || node_process.default.env["KUBB_DISABLE_TELEMETRY"] === "true";
758
+ }
759
+ /**
760
+ * Build an anonymous telemetry payload from a completed generation run.
761
+ */
762
+ static build(options) {
763
+ const [seconds, nanoseconds] = node_process.default.hrtime(options.hrStart);
764
+ const duration = Math.round(seconds * 1e3 + nanoseconds / 1e6);
765
+ return {
766
+ command: options.command,
767
+ kubbVersion: options.kubbVersion,
768
+ nodeVersion: node_process.default.versions.node.split(".")[0],
769
+ platform: node_os.default.platform(),
770
+ ci: isCIEnvironment(),
771
+ plugins: options.plugins ?? [],
772
+ duration,
773
+ filesCreated: options.filesCreated ?? 0,
774
+ status: options.status
775
+ };
776
+ }
777
+ /**
778
+ * Convert a {@link TelemetryEvent} into an OTLP-compatible JSON trace payload.
779
+ * See https://opentelemetry.io/docs/languages/sdk-configuration/otlp-exporter/
780
+ */
781
+ static buildOtlpPayload(event) {
782
+ const traceId = (0, node_crypto.randomBytes)(16).toString("hex");
783
+ const spanId = (0, node_crypto.randomBytes)(8).toString("hex");
784
+ const endTimeNs = BigInt(Date.now()) * 1000000n;
785
+ const startTimeNs = endTimeNs - BigInt(event.duration) * 1000000n;
786
+ const attributes = [
787
+ {
788
+ key: "kubb.command",
789
+ value: { stringValue: event.command }
790
+ },
791
+ {
792
+ key: "kubb.version",
793
+ value: { stringValue: event.kubbVersion }
794
+ },
795
+ {
796
+ key: "kubb.node_version",
797
+ value: { stringValue: event.nodeVersion }
798
+ },
799
+ {
800
+ key: "kubb.platform",
801
+ value: { stringValue: event.platform }
802
+ },
803
+ {
804
+ key: "kubb.ci",
805
+ value: { boolValue: event.ci }
806
+ },
807
+ {
808
+ key: "kubb.files_created",
809
+ value: { intValue: event.filesCreated }
810
+ },
811
+ {
812
+ key: "kubb.status",
813
+ value: { stringValue: event.status }
814
+ },
815
+ {
816
+ key: "kubb.plugins",
817
+ value: { arrayValue: { values: event.plugins.map((p) => ({ kvlistValue: { values: [{
818
+ key: "name",
819
+ value: { stringValue: p.name }
820
+ }, {
821
+ key: "options",
822
+ value: { stringValue: JSON.stringify({
823
+ ...p.options,
824
+ usedEnumNames: void 0
825
+ }) }
826
+ }] } })) } }
827
+ }
828
+ ];
829
+ return { resourceSpans: [{
830
+ resource: { attributes: [
831
+ {
832
+ key: "service.name",
833
+ value: { stringValue: "kubb-core" }
834
+ },
835
+ {
836
+ key: "service.version",
837
+ value: { stringValue: event.kubbVersion }
838
+ },
839
+ {
840
+ key: "telemetry.sdk.language",
841
+ value: { stringValue: "nodejs" }
842
+ }
843
+ ] },
844
+ scopeSpans: [{
845
+ scope: {
846
+ name: "kubb-core",
847
+ version: event.kubbVersion
848
+ },
849
+ spans: [{
850
+ traceId,
851
+ spanId,
852
+ name: event.command,
853
+ kind: 1,
854
+ startTimeUnixNano: String(startTimeNs),
855
+ endTimeUnixNano: String(endTimeNs),
856
+ attributes,
857
+ status: { code: event.status === "success" ? 1 : 2 }
858
+ }]
859
+ }]
860
+ }] };
861
+ }
862
+ /**
863
+ * Send an anonymous telemetry event to the Kubb OTLP endpoint. Respects `DO_NOT_TRACK` and
864
+ * `KUBB_DISABLE_TELEMETRY`, and fails silently so telemetry never interrupts a run.
865
+ */
866
+ static async send(event) {
867
+ if (Telemetry.isDisabled()) return;
868
+ await executeIfOnline(async () => {
869
+ try {
870
+ await fetch(`${require_memoryStorage.OTLP_ENDPOINT}/v1/traces`, {
871
+ method: "POST",
872
+ headers: {
873
+ "Content-Type": "application/json",
874
+ "Kubb-Telemetry-Version": "1",
875
+ "Kubb-Telemetry-Source": "kubb-core"
876
+ },
877
+ body: JSON.stringify(Telemetry.buildOtlpPayload(event)),
878
+ signal: AbortSignal.timeout(5e3)
879
+ });
880
+ } catch (_e) {}
881
+ });
882
+ }
883
+ };
884
+ //#endregion
1262
885
  //#region src/createRenderer.ts
1263
886
  /**
1264
- * Creates a renderer factory for use in generator definitions.
887
+ * Defines a renderer factory. Renderers turn the generator's return value
888
+ * (JSX, a template string, a tree of any shape) into `FileNode`s that get
889
+ * written to disk.
1265
890
  *
1266
- * Wrap your renderer factory function with this helper to register it as the
1267
- * renderer for a generator. Core will call this factory once per render cycle
1268
- * to obtain a fresh renderer instance.
891
+ * Use this to support output formats beyond JSX, for instance, a Handlebars
892
+ * renderer, a string-template renderer, or a renderer that writes binary
893
+ * files. Plugins and generators pick the renderer to use via the `renderer`
894
+ * field on `defineGenerator`.
1269
895
  *
1270
- * @example
896
+ * @example A minimal renderer that wraps a custom runtime
1271
897
  * ```ts
1272
- * // packages/renderer-jsx/src/index.ts
1273
- * export const jsxRenderer = createRenderer(() => {
1274
- * const runtime = new Runtime()
898
+ * import { createRenderer } from '@kubb/core'
899
+ *
900
+ * export const myRenderer = createRenderer(() => {
901
+ * const runtime = new MyRuntime()
1275
902
  * return {
1276
- * async render(element) { await runtime.render(element) },
1277
- * get files() { return runtime.nodes },
1278
- * unmount(error) { runtime.unmount(error) },
903
+ * async render(element) {
904
+ * await runtime.render(element)
905
+ * },
906
+ * get files() {
907
+ * return runtime.files
908
+ * },
909
+ * dispose() {
910
+ * runtime.dispose()
911
+ * },
912
+ * unmount(error) {
913
+ * runtime.dispose(error)
914
+ * },
915
+ * [Symbol.dispose]() {
916
+ * this.dispose()
917
+ * },
1279
918
  * }
1280
919
  * })
1281
- *
1282
- * // packages/plugin-zod/src/generators/zodGenerator.tsx
1283
- * import { jsxRenderer } from '@kubb/renderer-jsx'
1284
- * export const zodGenerator = defineGenerator<PluginZod>({
1285
- * name: 'zod',
1286
- * renderer: jsxRenderer,
1287
- * schema(node, options) { return <File ...>...</File> },
1288
- * })
1289
920
  * ```
1290
921
  */
1291
922
  function createRenderer(factory) {
@@ -1294,47 +925,50 @@ function createRenderer(factory) {
1294
925
  //#endregion
1295
926
  //#region src/defineGenerator.ts
1296
927
  /**
1297
- * Defines a generator. Returns the object as-is with correct `this` typings.
1298
- * `applyHookResult` handles renderer elements and `File[]` uniformly using
1299
- * the generator's declared `renderer` factory.
1300
- */
1301
- function defineGenerator(generator) {
1302
- return generator;
1303
- }
1304
- //#endregion
1305
- //#region src/defineLogger.ts
1306
- /**
1307
- * Wraps a logger definition into a typed {@link Logger}.
928
+ * Defines a generator: a unit of work that runs during the plugin's AST walk
929
+ * and produces files. Plugins register generators via `ctx.addGenerator()`
930
+ * inside `kubb:plugin:setup`.
1308
931
  *
1309
- * @example
1310
- * ```ts
1311
- * export const myLogger = defineLogger({
1312
- * name: 'my-logger',
1313
- * install(context, options) {
1314
- * context.on('kubb:info', (message) => console.log('ℹ', message))
1315
- * context.on('kubb:error', (error) => console.error('✗', error.message))
932
+ * The returned object is the input as-is, but with `this` types preserved so
933
+ * `schema`/`operation`/`operations` methods are correctly typed against the
934
+ * plugin's `PluginFactoryOptions`. Renderer elements and `FileNode[]` returns
935
+ * are both handled by the runtime, so pick whichever style fits.
936
+ *
937
+ * @example JSX-based schema generator
938
+ * ```tsx
939
+ * import { defineGenerator } from '@kubb/core'
940
+ * import { jsxRenderer } from '@kubb/renderer-jsx'
941
+ *
942
+ * export const typeGenerator = defineGenerator({
943
+ * name: 'typescript',
944
+ * renderer: jsxRenderer,
945
+ * schema(node, ctx) {
946
+ * return (
947
+ * <File path={`${ctx.root}/${node.name}.ts`}>
948
+ * <Type node={node} resolver={ctx.resolver} />
949
+ * </File>
950
+ * )
1316
951
  * },
1317
952
  * })
1318
953
  * ```
1319
954
  */
1320
- function defineLogger(logger) {
1321
- return logger;
955
+ function defineGenerator(generator) {
956
+ return generator;
1322
957
  }
1323
958
  //#endregion
1324
959
  //#region src/defineMiddleware.ts
1325
960
  /**
1326
- * Creates a middleware factory using the hook-style `hooks` API.
961
+ * Creates a middleware factory. Middleware fires after every plugin handler
962
+ * for the same event, which makes it the natural place for post-processing
963
+ * (barrel files, lint runs, audit logs).
1327
964
  *
1328
- * Middleware handlers fire after all plugin handlers for any given event, making them ideal for post-processing, logging, and auditing.
1329
- * Per-build state (such as accumulators) belongs inside the factory closure so each `createKubb` invocation gets its own isolated instance.
965
+ * Per-build state belongs inside the factory closure so each `createKubb`
966
+ * invocation gets its own isolated instance.
1330
967
  *
1331
- * @note The factory can accept typed options. See examples for using options and per-build state patterns.
1332
- *
1333
- * @example
968
+ * @example Stateless middleware
1334
969
  * ```ts
1335
970
  * import { defineMiddleware } from '@kubb/core'
1336
971
  *
1337
- * // Stateless middleware
1338
972
  * export const logMiddleware = defineMiddleware(() => ({
1339
973
  * name: 'log-middleware',
1340
974
  * hooks: {
@@ -1343,8 +977,12 @@ function defineLogger(logger) {
1343
977
  * },
1344
978
  * },
1345
979
  * }))
980
+ * ```
981
+ *
982
+ * @example Middleware with options and per-build state
983
+ * ```ts
984
+ * import { defineMiddleware } from '@kubb/core'
1346
985
  *
1347
- * // Middleware with options and per-build state
1348
986
  * export const prefixMiddleware = defineMiddleware((options: { prefix: string } = { prefix: '' }) => {
1349
987
  * const seen = new Set<string>()
1350
988
  * return {
@@ -1364,20 +1002,23 @@ function defineMiddleware(factory) {
1364
1002
  //#endregion
1365
1003
  //#region src/defineParser.ts
1366
1004
  /**
1367
- * Defines a parser with type safety. Creates parsers that transform generated files to strings based on their extension.
1368
- *
1369
- * @note Call the returned factory with optional options to instantiate the parser.
1005
+ * Defines a parser with type-safe `this`. Used to register handlers for new
1006
+ * file extensions or to plug a non-TypeScript output into the build.
1370
1007
  *
1371
1008
  * @example
1372
1009
  * ```ts
1373
- * import { defineParser } from '@kubb/core'
1010
+ * import { defineParser, ast } from '@kubb/core'
1374
1011
  *
1375
1012
  * export const jsonParser = defineParser({
1376
1013
  * name: 'json',
1377
1014
  * extNames: ['.json'],
1378
1015
  * parse(file) {
1379
- * const { extractStringsFromNodes } = await import('@kubb/ast')
1380
- * return file.sources.map((s) => extractStringsFromNodes(s.nodes ?? [])).join('\n')
1016
+ * return file.sources
1017
+ * .map((source) => ast.extractStringsFromNodes(source.nodes ?? []))
1018
+ * .join('\n')
1019
+ * },
1020
+ * print(...nodes) {
1021
+ * return nodes.map(String).join('\n')
1381
1022
  * },
1382
1023
  * })
1383
1024
  * ```
@@ -1386,108 +1027,34 @@ function defineParser(parser) {
1386
1027
  return parser;
1387
1028
  }
1388
1029
  //#endregion
1389
- //#region src/definePlugin.ts
1390
- /**
1391
- * Wraps a factory function and returns a typed `Plugin` with lifecycle handlers grouped under `hooks`.
1392
- *
1393
- * Handlers live in a single `hooks` object (inspired by Astro integrations).
1394
- * All lifecycle events from `KubbHooks` are available for subscription.
1395
- *
1396
- * @note For real plugins, use a `PluginFactoryOptions` type parameter to get type-safe context in `kubb:plugin:setup`.
1397
- * Plugin names should follow the convention `plugin-<feature>` (e.g., `plugin-react-query`, `plugin-zod`).
1398
- *
1399
- * @example
1400
- * ```ts
1401
- * import { definePlugin } from '@kubb/core'
1402
- *
1403
- * export const pluginTs = definePlugin((options: { prefix?: string } = {}) => ({
1404
- * name: 'plugin-ts',
1405
- * hooks: {
1406
- * 'kubb:plugin:setup'(ctx) {
1407
- * ctx.setResolver(resolverTs)
1408
- * },
1409
- * },
1410
- * }))
1411
- * ```
1412
- */
1413
- function definePlugin(factory) {
1414
- return (options) => factory(options ?? {});
1415
- }
1416
- //#endregion
1417
- //#region src/storages/memoryStorage.ts
1418
- /**
1419
- * In-memory storage driver. Useful for testing and dry-run scenarios where
1420
- * generated output should be captured without touching the filesystem.
1421
- *
1422
- * All data lives in a `Map` scoped to the storage instance and is discarded
1423
- * when the instance is garbage-collected.
1424
- *
1425
- * @example
1426
- * ```ts
1427
- * import { memoryStorage } from '@kubb/core'
1428
- * import { defineConfig } from 'kubb'
1429
- *
1430
- * export default defineConfig({
1431
- * input: { path: './petStore.yaml' },
1432
- * output: { path: './src/gen' },
1433
- * storage: memoryStorage(),
1434
- * })
1435
- * ```
1436
- */
1437
- const memoryStorage = createStorage(() => {
1438
- const store = /* @__PURE__ */ new Map();
1439
- return {
1440
- name: "memory",
1441
- async hasItem(key) {
1442
- return store.has(key);
1443
- },
1444
- async getItem(key) {
1445
- return store.get(key) ?? null;
1446
- },
1447
- async setItem(key, value) {
1448
- store.set(key, value);
1449
- },
1450
- async removeItem(key) {
1451
- store.delete(key);
1452
- },
1453
- async getKeys(base) {
1454
- const keys = [...store.keys()];
1455
- return base ? keys.filter((k) => k.startsWith(base)) : keys;
1456
- },
1457
- async clear(base) {
1458
- if (!base) {
1459
- store.clear();
1460
- return;
1461
- }
1462
- for (const key of store.keys()) if (key.startsWith(base)) store.delete(key);
1463
- }
1464
- };
1465
- });
1466
- //#endregion
1467
- exports.AsyncEventEmitter = AsyncEventEmitter;
1468
- exports.FileManager = require_PluginDriver.FileManager;
1469
- exports.FileProcessor = FileProcessor;
1470
- exports.PluginDriver = require_PluginDriver.PluginDriver;
1471
- exports.URLPath = URLPath;
1030
+ exports.AsyncEventEmitter = require_memoryStorage.AsyncEventEmitter;
1031
+ exports.Diagnostics = require_memoryStorage.Diagnostics;
1032
+ exports.KubbDriver = require_memoryStorage.KubbDriver;
1033
+ exports.Telemetry = Telemetry;
1034
+ exports.URLPath = require_memoryStorage.URLPath;
1472
1035
  Object.defineProperty(exports, "ast", {
1473
1036
  enumerable: true,
1474
1037
  get: function() {
1475
1038
  return _kubb_ast;
1476
1039
  }
1477
1040
  });
1041
+ exports.cliReporter = cliReporter;
1478
1042
  exports.createAdapter = createAdapter;
1479
1043
  exports.createKubb = createKubb;
1480
1044
  exports.createRenderer = createRenderer;
1481
- exports.createStorage = createStorage;
1045
+ exports.createReporter = createReporter;
1046
+ exports.createStorage = require_memoryStorage.createStorage;
1482
1047
  exports.defineGenerator = defineGenerator;
1483
1048
  exports.defineLogger = defineLogger;
1484
1049
  exports.defineMiddleware = defineMiddleware;
1485
1050
  exports.defineParser = defineParser;
1486
- exports.definePlugin = definePlugin;
1487
- exports.defineResolver = require_PluginDriver.defineResolver;
1051
+ exports.definePlugin = require_memoryStorage.definePlugin;
1052
+ exports.defineResolver = require_memoryStorage.defineResolver;
1053
+ exports.fileReporter = fileReporter;
1488
1054
  exports.fsStorage = fsStorage;
1489
- exports.isInputPath = isInputPath;
1490
- exports.logLevel = require_PluginDriver.logLevel;
1491
- exports.memoryStorage = memoryStorage;
1055
+ exports.jsonReporter = jsonReporter;
1056
+ exports.logLevel = logLevel;
1057
+ exports.memoryStorage = require_memoryStorage.memoryStorage;
1058
+ exports.selectReporters = selectReporters;
1492
1059
 
1493
1060
  //# sourceMappingURL=index.cjs.map