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