@kubb/core 5.0.0-beta.19 → 5.0.0-beta.20
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{PluginDriver-DXp767s2.cjs → KubbDriver-BXSnJ3qM.cjs} +546 -102
- package/dist/KubbDriver-BXSnJ3qM.cjs.map +1 -0
- package/dist/{PluginDriver-uNex0SAr.js → KubbDriver-Cxii_rBp.js} +522 -96
- package/dist/KubbDriver-Cxii_rBp.js.map +1 -0
- package/dist/{createKubb-BJGymYhe.d.ts → createKubb-Dcmtjqds.d.ts} +32 -62
- package/dist/index.cjs +257 -684
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +248 -675
- package/dist/index.js.map +1 -1
- package/dist/mocks.cjs +9 -10
- package/dist/mocks.cjs.map +1 -1
- package/dist/mocks.d.ts +5 -5
- package/dist/mocks.js +5 -6
- package/dist/mocks.js.map +1 -1
- package/package.json +4 -4
- package/src/{PluginDriver.ts → KubbDriver.ts} +175 -88
- package/src/constants.ts +4 -4
- package/src/createAdapter.ts +0 -8
- package/src/createKubb.ts +293 -469
- package/src/defineGenerator.ts +9 -8
- package/src/definePlugin.ts +5 -5
- package/src/defineResolver.ts +26 -40
- package/src/index.ts +1 -1
- package/src/mocks.ts +8 -8
- package/dist/PluginDriver-DXp767s2.cjs.map +0 -1
- package/dist/PluginDriver-uNex0SAr.js.map +0 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import "./chunk--u3MIqq1.js";
|
|
2
2
|
import path, { extname, resolve } from "node:path";
|
|
3
|
-
import { createFile, isOperationNode, isSchemaNode } from "@kubb/ast";
|
|
3
|
+
import { createFile, createStreamInput, isOperationNode, isSchemaNode } from "@kubb/ast";
|
|
4
4
|
import { deflateSync } from "fflate";
|
|
5
5
|
import { x } from "tinyexec";
|
|
6
6
|
//#region ../../internals/utils/src/casing.ts
|
|
@@ -67,6 +67,381 @@ function pascalCase(text, { isFile, prefix = "", suffix = "" } = {}) {
|
|
|
67
67
|
return toCamelOrPascal(`${prefix} ${text} ${suffix}`, true);
|
|
68
68
|
}
|
|
69
69
|
//#endregion
|
|
70
|
+
//#region ../../internals/utils/src/promise.ts
|
|
71
|
+
function* chunks(arr, size) {
|
|
72
|
+
for (let i = 0; i < arr.length; i += size) yield arr.slice(i, i + size);
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Slices `source` into batches of `concurrency` items and awaits `process` for each batch.
|
|
76
|
+
* Accepts both plain arrays (sync) and `AsyncIterable` (streaming).
|
|
77
|
+
*
|
|
78
|
+
* `process` controls whether items inside a batch run in parallel; this helper only
|
|
79
|
+
* controls batch size and per-batch flushing.
|
|
80
|
+
*
|
|
81
|
+
* @example
|
|
82
|
+
* ```ts
|
|
83
|
+
* // parallel dispatch inside each batch
|
|
84
|
+
* await forBatches(schemas, (batch) => Promise.all(batch.map(process)), { concurrency: 8 })
|
|
85
|
+
*
|
|
86
|
+
* // async iterable with a flush after every batch
|
|
87
|
+
* await forBatches(stream.schemas, (batch) => dispatch(batch), { concurrency: 8, flush })
|
|
88
|
+
* ```
|
|
89
|
+
*/
|
|
90
|
+
async function forBatches(source, process, options) {
|
|
91
|
+
const { concurrency, flush } = options;
|
|
92
|
+
if (Array.isArray(source)) {
|
|
93
|
+
for (const batch of chunks(source, concurrency)) {
|
|
94
|
+
await process(batch);
|
|
95
|
+
if (flush) await flush();
|
|
96
|
+
}
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
const batch = [];
|
|
100
|
+
for await (const item of source) {
|
|
101
|
+
batch.push(item);
|
|
102
|
+
if (batch.length >= concurrency) {
|
|
103
|
+
await process(batch.splice(0));
|
|
104
|
+
if (flush) await flush();
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
if (batch.length > 0) {
|
|
108
|
+
await process(batch.splice(0));
|
|
109
|
+
if (flush) await flush();
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Runs `work`, passing `flush` as its periodic-flush callback, then calls
|
|
114
|
+
* `flush` once more to drain any items that did not cross a flush boundary.
|
|
115
|
+
*
|
|
116
|
+
* @example
|
|
117
|
+
* ```ts
|
|
118
|
+
* await withDrain(
|
|
119
|
+
* (flush) => processItems(items, { flush }),
|
|
120
|
+
* () => writeRemainingFiles(),
|
|
121
|
+
* )
|
|
122
|
+
* ```
|
|
123
|
+
*/
|
|
124
|
+
async function withDrain(work, flush) {
|
|
125
|
+
await work(flush);
|
|
126
|
+
await flush();
|
|
127
|
+
}
|
|
128
|
+
/** Returns `true` when `result` is a thenable `Promise`.
|
|
129
|
+
*
|
|
130
|
+
* @example
|
|
131
|
+
* ```ts
|
|
132
|
+
* isPromise(Promise.resolve(1)) // true
|
|
133
|
+
* isPromise(42) // false
|
|
134
|
+
* ```
|
|
135
|
+
*/
|
|
136
|
+
function isPromise(result) {
|
|
137
|
+
return result !== null && result !== void 0 && typeof result["then"] === "function";
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Wraps `factory` with a keyed cache backed by the provided store.
|
|
141
|
+
*
|
|
142
|
+
* Pass a `WeakMap` for object keys (results are GC-eligible when the key is
|
|
143
|
+
* collected) or a `Map` for primitive keys. For multi-argument functions,
|
|
144
|
+
* nest two `memoize` calls — the outer keyed by the first argument, the
|
|
145
|
+
* inner (created once per outer miss) keyed by the second.
|
|
146
|
+
*
|
|
147
|
+
* Because the cache is owned by the caller, it can be shared, inspected, or
|
|
148
|
+
* cleared independently of the memoized function.
|
|
149
|
+
*
|
|
150
|
+
* @example Single WeakMap key
|
|
151
|
+
* ```ts
|
|
152
|
+
* const cache = new WeakMap<SchemaNode, Set<string>>()
|
|
153
|
+
* const getRefs = memoize(cache, (node) => collectRefs(node))
|
|
154
|
+
* ```
|
|
155
|
+
*
|
|
156
|
+
* @example Single Map key (primitive)
|
|
157
|
+
* ```ts
|
|
158
|
+
* const cache = new Map<string, Resolver>()
|
|
159
|
+
* const getResolver = memoize(cache, (name) => buildResolver(name))
|
|
160
|
+
* ```
|
|
161
|
+
*
|
|
162
|
+
* @example Two-level (object + primitive)
|
|
163
|
+
* ```ts
|
|
164
|
+
* const outer = new WeakMap<Params[], Map<string, Params[]>>()
|
|
165
|
+
* const fn = memoize(outer, (params) => memoize(new Map(), (key) => transform(params, key)))
|
|
166
|
+
* fn(params)('camelcase')
|
|
167
|
+
* ```
|
|
168
|
+
*/
|
|
169
|
+
function memoize(store, factory) {
|
|
170
|
+
return (key) => {
|
|
171
|
+
if (store.has(key)) return store.get(key);
|
|
172
|
+
const value = factory(key);
|
|
173
|
+
store.set(key, value);
|
|
174
|
+
return value;
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* Wraps a plain array in a reusable `AsyncIterable`.
|
|
179
|
+
* Each `[Symbol.asyncIterator]()` call returns a fresh generator so the
|
|
180
|
+
* iterable can be consumed multiple times (e.g. once per plugin pre-scan).
|
|
181
|
+
*
|
|
182
|
+
* @example
|
|
183
|
+
* ```ts
|
|
184
|
+
* const stream = arrayToAsyncIterable([1, 2, 3])
|
|
185
|
+
* for await (const n of stream) console.log(n) // 1, 2, 3
|
|
186
|
+
* ```
|
|
187
|
+
*/
|
|
188
|
+
function arrayToAsyncIterable(arr) {
|
|
189
|
+
return { [Symbol.asyncIterator]() {
|
|
190
|
+
return (async function* () {
|
|
191
|
+
yield* arr;
|
|
192
|
+
})();
|
|
193
|
+
} };
|
|
194
|
+
}
|
|
195
|
+
//#endregion
|
|
196
|
+
//#region ../../internals/utils/src/reserved.ts
|
|
197
|
+
/**
|
|
198
|
+
* JavaScript and Java reserved words.
|
|
199
|
+
* @link https://github.com/jonschlinkert/reserved/blob/master/index.js
|
|
200
|
+
*/
|
|
201
|
+
const reservedWords = new Set([
|
|
202
|
+
"abstract",
|
|
203
|
+
"arguments",
|
|
204
|
+
"boolean",
|
|
205
|
+
"break",
|
|
206
|
+
"byte",
|
|
207
|
+
"case",
|
|
208
|
+
"catch",
|
|
209
|
+
"char",
|
|
210
|
+
"class",
|
|
211
|
+
"const",
|
|
212
|
+
"continue",
|
|
213
|
+
"debugger",
|
|
214
|
+
"default",
|
|
215
|
+
"delete",
|
|
216
|
+
"do",
|
|
217
|
+
"double",
|
|
218
|
+
"else",
|
|
219
|
+
"enum",
|
|
220
|
+
"eval",
|
|
221
|
+
"export",
|
|
222
|
+
"extends",
|
|
223
|
+
"false",
|
|
224
|
+
"final",
|
|
225
|
+
"finally",
|
|
226
|
+
"float",
|
|
227
|
+
"for",
|
|
228
|
+
"function",
|
|
229
|
+
"goto",
|
|
230
|
+
"if",
|
|
231
|
+
"implements",
|
|
232
|
+
"import",
|
|
233
|
+
"in",
|
|
234
|
+
"instanceof",
|
|
235
|
+
"int",
|
|
236
|
+
"interface",
|
|
237
|
+
"let",
|
|
238
|
+
"long",
|
|
239
|
+
"native",
|
|
240
|
+
"new",
|
|
241
|
+
"null",
|
|
242
|
+
"package",
|
|
243
|
+
"private",
|
|
244
|
+
"protected",
|
|
245
|
+
"public",
|
|
246
|
+
"return",
|
|
247
|
+
"short",
|
|
248
|
+
"static",
|
|
249
|
+
"super",
|
|
250
|
+
"switch",
|
|
251
|
+
"synchronized",
|
|
252
|
+
"this",
|
|
253
|
+
"throw",
|
|
254
|
+
"throws",
|
|
255
|
+
"transient",
|
|
256
|
+
"true",
|
|
257
|
+
"try",
|
|
258
|
+
"typeof",
|
|
259
|
+
"var",
|
|
260
|
+
"void",
|
|
261
|
+
"volatile",
|
|
262
|
+
"while",
|
|
263
|
+
"with",
|
|
264
|
+
"yield",
|
|
265
|
+
"Array",
|
|
266
|
+
"Date",
|
|
267
|
+
"hasOwnProperty",
|
|
268
|
+
"Infinity",
|
|
269
|
+
"isFinite",
|
|
270
|
+
"isNaN",
|
|
271
|
+
"isPrototypeOf",
|
|
272
|
+
"length",
|
|
273
|
+
"Math",
|
|
274
|
+
"name",
|
|
275
|
+
"NaN",
|
|
276
|
+
"Number",
|
|
277
|
+
"Object",
|
|
278
|
+
"prototype",
|
|
279
|
+
"String",
|
|
280
|
+
"toString",
|
|
281
|
+
"undefined",
|
|
282
|
+
"valueOf"
|
|
283
|
+
]);
|
|
284
|
+
/**
|
|
285
|
+
* Returns `true` when `name` is a syntactically valid JavaScript variable name.
|
|
286
|
+
*
|
|
287
|
+
* @example
|
|
288
|
+
* ```ts
|
|
289
|
+
* isValidVarName('status') // true
|
|
290
|
+
* isValidVarName('class') // false (reserved word)
|
|
291
|
+
* isValidVarName('42foo') // false (starts with digit)
|
|
292
|
+
* ```
|
|
293
|
+
*/
|
|
294
|
+
function isValidVarName(name) {
|
|
295
|
+
if (!name || reservedWords.has(name)) return false;
|
|
296
|
+
return /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(name);
|
|
297
|
+
}
|
|
298
|
+
//#endregion
|
|
299
|
+
//#region ../../internals/utils/src/urlPath.ts
|
|
300
|
+
/**
|
|
301
|
+
* Parses and transforms an OpenAPI/Swagger path string into various URL formats.
|
|
302
|
+
*
|
|
303
|
+
* @example
|
|
304
|
+
* const p = new URLPath('/pet/{petId}')
|
|
305
|
+
* p.URL // '/pet/:petId'
|
|
306
|
+
* p.template // '`/pet/${petId}`'
|
|
307
|
+
*/
|
|
308
|
+
var URLPath = class {
|
|
309
|
+
/**
|
|
310
|
+
* The raw OpenAPI/Swagger path string, e.g. `/pet/{petId}`.
|
|
311
|
+
*/
|
|
312
|
+
path;
|
|
313
|
+
#options;
|
|
314
|
+
constructor(path, options = {}) {
|
|
315
|
+
this.path = path;
|
|
316
|
+
this.#options = options;
|
|
317
|
+
}
|
|
318
|
+
/** Converts the OpenAPI path to Express-style colon syntax, e.g. `/pet/{petId}` → `/pet/:petId`.
|
|
319
|
+
*
|
|
320
|
+
* @example
|
|
321
|
+
* ```ts
|
|
322
|
+
* new URLPath('/pet/{petId}').URL // '/pet/:petId'
|
|
323
|
+
* ```
|
|
324
|
+
*/
|
|
325
|
+
get URL() {
|
|
326
|
+
return this.toURLPath();
|
|
327
|
+
}
|
|
328
|
+
/** Returns `true` when `path` is a fully-qualified URL (e.g. starts with `https://`).
|
|
329
|
+
*
|
|
330
|
+
* @example
|
|
331
|
+
* ```ts
|
|
332
|
+
* new URLPath('https://petstore.swagger.io/v2/pet').isURL // true
|
|
333
|
+
* new URLPath('/pet/{petId}').isURL // false
|
|
334
|
+
* ```
|
|
335
|
+
*/
|
|
336
|
+
get isURL() {
|
|
337
|
+
try {
|
|
338
|
+
return !!new URL(this.path).href;
|
|
339
|
+
} catch {
|
|
340
|
+
return false;
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
/**
|
|
344
|
+
* Converts the OpenAPI path to a TypeScript template literal string.
|
|
345
|
+
*
|
|
346
|
+
* @example
|
|
347
|
+
* new URLPath('/pet/{petId}').template // '`/pet/${petId}`'
|
|
348
|
+
* new URLPath('/account/monetary-accountID').template // '`/account/${monetaryAccountId}`'
|
|
349
|
+
*/
|
|
350
|
+
get template() {
|
|
351
|
+
return this.toTemplateString();
|
|
352
|
+
}
|
|
353
|
+
/** Returns the path and its extracted params as a structured `URLObject`, or as a stringified expression when `stringify` is set.
|
|
354
|
+
*
|
|
355
|
+
* @example
|
|
356
|
+
* ```ts
|
|
357
|
+
* new URLPath('/pet/{petId}').object
|
|
358
|
+
* // { url: '/pet/:petId', params: { petId: 'petId' } }
|
|
359
|
+
* ```
|
|
360
|
+
*/
|
|
361
|
+
get object() {
|
|
362
|
+
return this.toObject();
|
|
363
|
+
}
|
|
364
|
+
/** Returns a map of path parameter names, or `undefined` when the path has no parameters.
|
|
365
|
+
*
|
|
366
|
+
* @example
|
|
367
|
+
* ```ts
|
|
368
|
+
* new URLPath('/pet/{petId}').params // { petId: 'petId' }
|
|
369
|
+
* new URLPath('/pet').params // undefined
|
|
370
|
+
* ```
|
|
371
|
+
*/
|
|
372
|
+
get params() {
|
|
373
|
+
return this.getParams();
|
|
374
|
+
}
|
|
375
|
+
#transformParam(raw) {
|
|
376
|
+
const param = isValidVarName(raw) ? raw : camelCase(raw);
|
|
377
|
+
return this.#options.casing === "camelcase" ? camelCase(param) : param;
|
|
378
|
+
}
|
|
379
|
+
/**
|
|
380
|
+
* Iterates over every `{param}` token in `path`, calling `fn` with the raw token and transformed name.
|
|
381
|
+
*/
|
|
382
|
+
#eachParam(fn) {
|
|
383
|
+
for (const match of this.path.matchAll(/\{([^}]+)\}/g)) {
|
|
384
|
+
const raw = match[1];
|
|
385
|
+
fn(raw, this.#transformParam(raw));
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
toObject({ type = "path", replacer, stringify } = {}) {
|
|
389
|
+
const object = {
|
|
390
|
+
url: type === "path" ? this.toURLPath() : this.toTemplateString({ replacer }),
|
|
391
|
+
params: this.getParams()
|
|
392
|
+
};
|
|
393
|
+
if (stringify) {
|
|
394
|
+
if (type === "template") return JSON.stringify(object).replaceAll("'", "").replaceAll(`"`, "");
|
|
395
|
+
if (object.params) return `{ url: '${object.url}', params: ${JSON.stringify(object.params).replaceAll("'", "").replaceAll(`"`, "")} }`;
|
|
396
|
+
return `{ url: '${object.url}' }`;
|
|
397
|
+
}
|
|
398
|
+
return object;
|
|
399
|
+
}
|
|
400
|
+
/**
|
|
401
|
+
* Converts the OpenAPI path to a TypeScript template literal string.
|
|
402
|
+
* An optional `replacer` can transform each extracted parameter name before interpolation.
|
|
403
|
+
*
|
|
404
|
+
* @example
|
|
405
|
+
* new URLPath('/pet/{petId}').toTemplateString() // '`/pet/${petId}`'
|
|
406
|
+
*/
|
|
407
|
+
toTemplateString({ prefix = "", replacer } = {}) {
|
|
408
|
+
return `\`${prefix}${this.path.split(/\{([^}]+)\}/).map((part, i) => {
|
|
409
|
+
if (i % 2 === 0) return part;
|
|
410
|
+
const param = this.#transformParam(part);
|
|
411
|
+
return `\${${replacer ? replacer(param) : param}}`;
|
|
412
|
+
}).join("")}\``;
|
|
413
|
+
}
|
|
414
|
+
/**
|
|
415
|
+
* Extracts all `{param}` segments from the path and returns them as a key-value map.
|
|
416
|
+
* An optional `replacer` transforms each parameter name in both key and value positions.
|
|
417
|
+
* Returns `undefined` when no path parameters are found.
|
|
418
|
+
*
|
|
419
|
+
* @example
|
|
420
|
+
* ```ts
|
|
421
|
+
* new URLPath('/pet/{petId}/tag/{tagId}').getParams()
|
|
422
|
+
* // { petId: 'petId', tagId: 'tagId' }
|
|
423
|
+
* ```
|
|
424
|
+
*/
|
|
425
|
+
getParams(replacer) {
|
|
426
|
+
const params = {};
|
|
427
|
+
this.#eachParam((_raw, param) => {
|
|
428
|
+
const key = replacer ? replacer(param) : param;
|
|
429
|
+
params[key] = key;
|
|
430
|
+
});
|
|
431
|
+
return Object.keys(params).length > 0 ? params : void 0;
|
|
432
|
+
}
|
|
433
|
+
/** Converts the OpenAPI path to Express-style colon syntax.
|
|
434
|
+
*
|
|
435
|
+
* @example
|
|
436
|
+
* ```ts
|
|
437
|
+
* new URLPath('/pet/{petId}').toURLPath() // '/pet/:petId'
|
|
438
|
+
* ```
|
|
439
|
+
*/
|
|
440
|
+
toURLPath() {
|
|
441
|
+
return this.path.replace(/\{([^}]+)\}/g, ":$1");
|
|
442
|
+
}
|
|
443
|
+
};
|
|
444
|
+
//#endregion
|
|
70
445
|
//#region src/constants.ts
|
|
71
446
|
/**
|
|
72
447
|
* Base URL for the Kubb Studio web app.
|
|
@@ -147,14 +522,12 @@ function testPattern(value, pattern) {
|
|
|
147
522
|
* Checks if an operation matches a pattern for a given filter type (`tag`, `operationId`, `path`, `method`).
|
|
148
523
|
*/
|
|
149
524
|
function matchesOperationPattern(node, type, pattern) {
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
default: return false;
|
|
157
|
-
}
|
|
525
|
+
if (type === "tag") return node.tags.some((tag) => testPattern(tag, pattern));
|
|
526
|
+
if (type === "operationId") return testPattern(node.operationId, pattern);
|
|
527
|
+
if (type === "path") return testPattern(node.path, pattern);
|
|
528
|
+
if (type === "method") return testPattern(node.method.toLowerCase(), pattern);
|
|
529
|
+
if (type === "contentType") return node.requestBody?.content?.some((c) => testPattern(c.contentType, pattern)) ?? false;
|
|
530
|
+
return false;
|
|
158
531
|
}
|
|
159
532
|
/**
|
|
160
533
|
* Checks if a schema matches a pattern for a given filter type (`schemaName`).
|
|
@@ -162,10 +535,8 @@ function matchesOperationPattern(node, type, pattern) {
|
|
|
162
535
|
* Returns `null` when the filter type doesn't apply to schemas.
|
|
163
536
|
*/
|
|
164
537
|
function matchesSchemaPattern(node, type, pattern) {
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
default: return null;
|
|
168
|
-
}
|
|
538
|
+
if (type === "schemaName") return node.name ? testPattern(node.name, pattern) : false;
|
|
539
|
+
return null;
|
|
169
540
|
}
|
|
170
541
|
/**
|
|
171
542
|
* Default name resolver used by `defineResolver`.
|
|
@@ -388,10 +759,9 @@ function buildDefaultBanner({ title, description, version, config }) {
|
|
|
388
759
|
*
|
|
389
760
|
* A user-supplied `output.banner` overrides the default Kubb "Generated by Kubb" notice.
|
|
390
761
|
* When no `output.banner` is set, the Kubb notice is used (including `title` and `version`
|
|
391
|
-
* from the
|
|
762
|
+
* from the document metadata when `meta` is provided).
|
|
392
763
|
*
|
|
393
|
-
* - When `output.banner` is a function
|
|
394
|
-
* - When `output.banner` is a function and `node` is absent, falls back to the Kubb notice.
|
|
764
|
+
* - When `output.banner` is a function, calls it with `meta` and returns the result.
|
|
395
765
|
* - When `output.banner` is a string, returns it directly.
|
|
396
766
|
* - When `config.output.defaultBanner` is `false`, returns `undefined`.
|
|
397
767
|
* - Otherwise returns the Kubb "Generated by Kubb" notice.
|
|
@@ -402,15 +772,15 @@ function buildDefaultBanner({ title, description, version, config }) {
|
|
|
402
772
|
* // → '// my banner'
|
|
403
773
|
* ```
|
|
404
774
|
*
|
|
405
|
-
* @example Function banner with
|
|
775
|
+
* @example Function banner with metadata
|
|
406
776
|
* ```ts
|
|
407
|
-
* defaultResolveBanner(
|
|
777
|
+
* defaultResolveBanner(meta, { output: { banner: (m) => `// v${m?.version}` }, config })
|
|
408
778
|
* // → '// v3.0.0'
|
|
409
779
|
* ```
|
|
410
780
|
*
|
|
411
781
|
* @example No user banner — Kubb notice with OAS metadata
|
|
412
782
|
* ```ts
|
|
413
|
-
* defaultResolveBanner(
|
|
783
|
+
* defaultResolveBanner(meta, { config })
|
|
414
784
|
* // → '/** Generated by Kubb ... Title: Pet Store ... *\/'
|
|
415
785
|
* ```
|
|
416
786
|
*
|
|
@@ -420,21 +790,20 @@ function buildDefaultBanner({ title, description, version, config }) {
|
|
|
420
790
|
* // → undefined
|
|
421
791
|
* ```
|
|
422
792
|
*/
|
|
423
|
-
function defaultResolveBanner(
|
|
424
|
-
if (typeof output?.banner === "function") return output.banner(
|
|
793
|
+
function defaultResolveBanner(meta, { output, config }) {
|
|
794
|
+
if (typeof output?.banner === "function") return output.banner(meta);
|
|
425
795
|
if (typeof output?.banner === "string") return output.banner;
|
|
426
796
|
if (config.output.defaultBanner === false) return;
|
|
427
797
|
return buildDefaultBanner({
|
|
428
|
-
title:
|
|
429
|
-
version:
|
|
798
|
+
title: meta?.title,
|
|
799
|
+
version: meta?.version,
|
|
430
800
|
config
|
|
431
801
|
});
|
|
432
802
|
}
|
|
433
803
|
/**
|
|
434
804
|
* Default footer resolver — returns the footer string for a generated file.
|
|
435
805
|
*
|
|
436
|
-
* - When `output.footer` is a function
|
|
437
|
-
* - When `output.footer` is a function and `node` is absent, returns `undefined`.
|
|
806
|
+
* - When `output.footer` is a function, calls it with `meta` and returns the result.
|
|
438
807
|
* - When `output.footer` is a string, returns it directly.
|
|
439
808
|
* - Otherwise returns `undefined`.
|
|
440
809
|
*
|
|
@@ -444,14 +813,14 @@ function defaultResolveBanner(node, { output, config }) {
|
|
|
444
813
|
* // → '// end of file'
|
|
445
814
|
* ```
|
|
446
815
|
*
|
|
447
|
-
* @example Function footer with
|
|
816
|
+
* @example Function footer with metadata
|
|
448
817
|
* ```ts
|
|
449
|
-
* defaultResolveFooter(
|
|
818
|
+
* defaultResolveFooter(meta, { output: { footer: (m) => `// ${m?.title}` }, config })
|
|
450
819
|
* // → '// Pet Store'
|
|
451
820
|
* ```
|
|
452
821
|
*/
|
|
453
|
-
function defaultResolveFooter(
|
|
454
|
-
if (typeof output?.footer === "function") return
|
|
822
|
+
function defaultResolveFooter(meta, { output }) {
|
|
823
|
+
if (typeof output?.footer === "function") return output.footer(meta);
|
|
455
824
|
if (typeof output?.footer === "string") return output.footer;
|
|
456
825
|
}
|
|
457
826
|
/**
|
|
@@ -664,11 +1033,11 @@ var FileManager = class {
|
|
|
664
1033
|
}
|
|
665
1034
|
};
|
|
666
1035
|
//#endregion
|
|
667
|
-
//#region src/
|
|
1036
|
+
//#region src/KubbDriver.ts
|
|
668
1037
|
function enforceOrder(enforce) {
|
|
669
1038
|
return enforce === "pre" ? -1 : enforce === "post" ? 1 : 0;
|
|
670
1039
|
}
|
|
671
|
-
var
|
|
1040
|
+
var KubbDriver = class KubbDriver {
|
|
672
1041
|
config;
|
|
673
1042
|
options;
|
|
674
1043
|
/**
|
|
@@ -676,25 +1045,34 @@ var PluginDriver = class PluginDriver {
|
|
|
676
1045
|
*
|
|
677
1046
|
* @example
|
|
678
1047
|
* ```ts
|
|
679
|
-
*
|
|
680
|
-
*
|
|
1048
|
+
* KubbDriver.getMode('src/gen/types.ts') // 'single'
|
|
1049
|
+
* KubbDriver.getMode('src/gen/types') // 'split'
|
|
681
1050
|
* ```
|
|
682
1051
|
*/
|
|
683
1052
|
static getMode(fileOrFolder) {
|
|
684
1053
|
return getMode(fileOrFolder);
|
|
685
1054
|
}
|
|
686
1055
|
/**
|
|
687
|
-
* The
|
|
688
|
-
*
|
|
1056
|
+
* The streaming `InputStreamNode` produced by the adapter.
|
|
1057
|
+
* Always set after adapter setup — parse-only adapters are wrapped automatically.
|
|
689
1058
|
*/
|
|
690
1059
|
inputNode = void 0;
|
|
1060
|
+
adapter = void 0;
|
|
691
1061
|
/**
|
|
692
|
-
*
|
|
693
|
-
*
|
|
1062
|
+
* Studio session state, kept together so `dispose()` can reset it atomically.
|
|
1063
|
+
*
|
|
1064
|
+
* - `source` holds the raw adapter source so `adapter.parse()` can be called lazily.
|
|
1065
|
+
* Intentionally outlives the build; cleared by `dispose()`.
|
|
1066
|
+
* - `isOpen` prevents opening the studio more than once per build.
|
|
1067
|
+
* - `inputNode` caches the parse promise so `adapter.parse()` is called at most once
|
|
1068
|
+
* per studio session, even when `openInStudio()` is called multiple times.
|
|
694
1069
|
*/
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
1070
|
+
#studio = {
|
|
1071
|
+
source: void 0,
|
|
1072
|
+
isOpen: false,
|
|
1073
|
+
inputNode: void 0
|
|
1074
|
+
};
|
|
1075
|
+
#middlewareListeners = [];
|
|
698
1076
|
/**
|
|
699
1077
|
* Central file store for all generated files.
|
|
700
1078
|
* Plugins should use `this.addFile()` / `this.upsertFile()` (via their context) to
|
|
@@ -706,23 +1084,29 @@ var PluginDriver = class PluginDriver {
|
|
|
706
1084
|
* Tracks which plugins have generators registered via `addGenerator()` (event-based path).
|
|
707
1085
|
* Used by the build loop to decide whether to emit generator events for a given plugin.
|
|
708
1086
|
*/
|
|
709
|
-
#
|
|
1087
|
+
#eventGeneratorPlugins = /* @__PURE__ */ new Set();
|
|
710
1088
|
#resolvers = /* @__PURE__ */ new Map();
|
|
711
1089
|
#defaultResolvers = /* @__PURE__ */ new Map();
|
|
712
1090
|
#hookListeners = /* @__PURE__ */ new Map();
|
|
713
1091
|
constructor(config, options) {
|
|
714
1092
|
this.config = config;
|
|
715
1093
|
this.options = options;
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
1094
|
+
this.adapter = config.adapter;
|
|
1095
|
+
}
|
|
1096
|
+
async setup() {
|
|
1097
|
+
const normalized = this.config.plugins.map((rawPlugin) => this.#normalizePlugin(rawPlugin));
|
|
1098
|
+
normalized.sort((a, b) => {
|
|
720
1099
|
if (b.dependencies?.includes(a.name)) return -1;
|
|
721
1100
|
if (a.dependencies?.includes(b.name)) return 1;
|
|
722
1101
|
return enforceOrder(a.enforce) - enforceOrder(b.enforce);
|
|
723
|
-
}).forEach((plugin) => {
|
|
724
|
-
this.plugins.set(plugin.name, plugin);
|
|
725
1102
|
});
|
|
1103
|
+
for (const plugin of normalized) {
|
|
1104
|
+
if (plugin.apply) plugin.apply(this.config);
|
|
1105
|
+
this.#registerPlugin(plugin);
|
|
1106
|
+
this.plugins.set(plugin.name, plugin);
|
|
1107
|
+
}
|
|
1108
|
+
if (this.config.middleware) for (const middleware of this.config.middleware) for (const event of Object.keys(middleware.hooks)) this.#registerMiddleware(event, middleware.hooks);
|
|
1109
|
+
if (this.config.adapter) await this.#registerAdapter(this.config.adapter);
|
|
726
1110
|
}
|
|
727
1111
|
get hooks() {
|
|
728
1112
|
return this.options.hooks;
|
|
@@ -731,19 +1115,48 @@ var PluginDriver = class PluginDriver {
|
|
|
731
1115
|
* Creates an `NormalizedPlugin` from a hook-style plugin and registers
|
|
732
1116
|
* its lifecycle handlers on the `AsyncEventEmitter`.
|
|
733
1117
|
*/
|
|
734
|
-
#normalizePlugin(
|
|
735
|
-
const
|
|
736
|
-
name:
|
|
737
|
-
dependencies:
|
|
738
|
-
enforce:
|
|
739
|
-
|
|
1118
|
+
#normalizePlugin(plugin) {
|
|
1119
|
+
const normalized = {
|
|
1120
|
+
name: plugin.name,
|
|
1121
|
+
dependencies: plugin.dependencies,
|
|
1122
|
+
enforce: plugin.enforce,
|
|
1123
|
+
hooks: plugin.hooks,
|
|
1124
|
+
options: plugin.options ?? {
|
|
740
1125
|
output: { path: "." },
|
|
741
1126
|
exclude: [],
|
|
742
1127
|
override: []
|
|
743
1128
|
}
|
|
744
1129
|
};
|
|
745
|
-
|
|
746
|
-
return
|
|
1130
|
+
if ("apply" in plugin && typeof plugin.apply === "function") normalized.apply = plugin.apply;
|
|
1131
|
+
return normalized;
|
|
1132
|
+
}
|
|
1133
|
+
async #registerAdapter(adapter) {
|
|
1134
|
+
const source = inputToAdapterSource(this.config);
|
|
1135
|
+
this.#studio.source = source;
|
|
1136
|
+
if (adapter.stream) {
|
|
1137
|
+
this.inputNode = await adapter.stream(source);
|
|
1138
|
+
await this.hooks.emit("kubb:debug", {
|
|
1139
|
+
date: /* @__PURE__ */ new Date(),
|
|
1140
|
+
logs: [`✓ Adapter '${adapter.name}' producing input stream`]
|
|
1141
|
+
});
|
|
1142
|
+
} else {
|
|
1143
|
+
const inputNode = await adapter.parse(source);
|
|
1144
|
+
this.inputNode = createStreamInput(arrayToAsyncIterable(inputNode.schemas), arrayToAsyncIterable(inputNode.operations), inputNode.meta);
|
|
1145
|
+
await this.hooks.emit("kubb:debug", {
|
|
1146
|
+
date: /* @__PURE__ */ new Date(),
|
|
1147
|
+
logs: [
|
|
1148
|
+
`✓ Adapter '${adapter.name}' resolved InputNode (wrapped as stream)`,
|
|
1149
|
+
` • Schemas: ${inputNode.schemas.length}`,
|
|
1150
|
+
` • Operations: ${inputNode.operations.length}`
|
|
1151
|
+
]
|
|
1152
|
+
});
|
|
1153
|
+
}
|
|
1154
|
+
}
|
|
1155
|
+
#registerMiddleware(event, middlewareHooks) {
|
|
1156
|
+
const handler = middlewareHooks[event];
|
|
1157
|
+
if (!handler) return;
|
|
1158
|
+
this.hooks.on(event, handler);
|
|
1159
|
+
this.#middlewareListeners.push([event, handler]);
|
|
747
1160
|
}
|
|
748
1161
|
/**
|
|
749
1162
|
* Registers a hook-style plugin's lifecycle handlers on the shared `AsyncEventEmitter`.
|
|
@@ -760,29 +1173,29 @@ var PluginDriver = class PluginDriver {
|
|
|
760
1173
|
*
|
|
761
1174
|
* @internal
|
|
762
1175
|
*/
|
|
763
|
-
|
|
764
|
-
const { hooks } =
|
|
1176
|
+
#registerPlugin(plugin) {
|
|
1177
|
+
const { hooks } = plugin;
|
|
765
1178
|
if (!hooks) return;
|
|
766
1179
|
if (hooks["kubb:plugin:setup"]) {
|
|
767
1180
|
const setupHandler = (globalCtx) => {
|
|
768
1181
|
const pluginCtx = {
|
|
769
1182
|
...globalCtx,
|
|
770
|
-
options:
|
|
1183
|
+
options: plugin.options ?? {},
|
|
771
1184
|
addGenerator: (gen) => {
|
|
772
|
-
this.registerGenerator(
|
|
1185
|
+
this.registerGenerator(plugin.name, gen);
|
|
773
1186
|
},
|
|
774
1187
|
setResolver: (resolver) => {
|
|
775
|
-
this.setPluginResolver(
|
|
1188
|
+
this.setPluginResolver(plugin.name, resolver);
|
|
776
1189
|
},
|
|
777
1190
|
setTransformer: (visitor) => {
|
|
778
|
-
|
|
1191
|
+
plugin.transformer = visitor;
|
|
779
1192
|
},
|
|
780
1193
|
setRenderer: (renderer) => {
|
|
781
|
-
|
|
1194
|
+
plugin.renderer = renderer;
|
|
782
1195
|
},
|
|
783
1196
|
setOptions: (opts) => {
|
|
784
|
-
|
|
785
|
-
...
|
|
1197
|
+
plugin.options = {
|
|
1198
|
+
...plugin.options,
|
|
786
1199
|
...opts
|
|
787
1200
|
};
|
|
788
1201
|
},
|
|
@@ -876,7 +1289,7 @@ var PluginDriver = class PluginDriver {
|
|
|
876
1289
|
this.hooks.on("kubb:generate:operations", operationsHandler);
|
|
877
1290
|
this.#trackHookListener("kubb:generate:operations", operationsHandler);
|
|
878
1291
|
}
|
|
879
|
-
this.#
|
|
1292
|
+
this.#eventGeneratorPlugins.add(pluginName);
|
|
880
1293
|
}
|
|
881
1294
|
/**
|
|
882
1295
|
* Returns `true` when at least one generator was registered for the given plugin
|
|
@@ -885,8 +1298,8 @@ var PluginDriver = class PluginDriver {
|
|
|
885
1298
|
* Used by the build loop to decide whether to walk the AST and emit generator events
|
|
886
1299
|
* for a plugin that has no static `plugin.generators`.
|
|
887
1300
|
*/
|
|
888
|
-
|
|
889
|
-
return this.#
|
|
1301
|
+
hasEventGenerators(pluginName) {
|
|
1302
|
+
return this.#eventGeneratorPlugins.has(pluginName);
|
|
890
1303
|
}
|
|
891
1304
|
/**
|
|
892
1305
|
* Unregisters all plugin lifecycle listeners from the shared event emitter.
|
|
@@ -897,12 +1310,17 @@ var PluginDriver = class PluginDriver {
|
|
|
897
1310
|
dispose() {
|
|
898
1311
|
for (const [event, handlers] of this.#hookListeners) for (const handler of handlers) this.hooks.off(event, handler);
|
|
899
1312
|
this.#hookListeners.clear();
|
|
900
|
-
this.#
|
|
1313
|
+
this.#eventGeneratorPlugins.clear();
|
|
901
1314
|
this.#resolvers.clear();
|
|
902
1315
|
this.#defaultResolvers.clear();
|
|
903
1316
|
this.fileManager.dispose();
|
|
904
1317
|
this.inputNode = void 0;
|
|
905
|
-
this
|
|
1318
|
+
this.#studio = {
|
|
1319
|
+
source: void 0,
|
|
1320
|
+
isOpen: false,
|
|
1321
|
+
inputNode: void 0
|
|
1322
|
+
};
|
|
1323
|
+
for (const [event, handler] of this.#middlewareListeners) this.hooks.off(event, handler);
|
|
906
1324
|
}
|
|
907
1325
|
[Symbol.dispose]() {
|
|
908
1326
|
this.dispose();
|
|
@@ -915,16 +1333,10 @@ var PluginDriver = class PluginDriver {
|
|
|
915
1333
|
}
|
|
916
1334
|
handlers.add(handler);
|
|
917
1335
|
}
|
|
918
|
-
#
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
name: "default",
|
|
923
|
-
pluginName
|
|
924
|
-
}));
|
|
925
|
-
this.#defaultResolvers.set(pluginName, resolver);
|
|
926
|
-
return resolver;
|
|
927
|
-
}
|
|
1336
|
+
#getDefaultResolver = memoize(this.#defaultResolvers, (pluginName) => defineResolver(() => ({
|
|
1337
|
+
name: "default",
|
|
1338
|
+
pluginName
|
|
1339
|
+
})));
|
|
928
1340
|
/**
|
|
929
1341
|
* Merges `partial` with the plugin's default resolver and stores the result.
|
|
930
1342
|
* Also mirrors it onto `plugin.resolver` so callers using `getPlugin(name).resolver`
|
|
@@ -932,7 +1344,7 @@ var PluginDriver = class PluginDriver {
|
|
|
932
1344
|
*/
|
|
933
1345
|
setPluginResolver(pluginName, partial) {
|
|
934
1346
|
const merged = {
|
|
935
|
-
...this.#
|
|
1347
|
+
...this.#getDefaultResolver(pluginName),
|
|
936
1348
|
...partial
|
|
937
1349
|
};
|
|
938
1350
|
this.#resolvers.set(pluginName, merged);
|
|
@@ -940,7 +1352,7 @@ var PluginDriver = class PluginDriver {
|
|
|
940
1352
|
if (plugin) plugin.resolver = merged;
|
|
941
1353
|
}
|
|
942
1354
|
getResolver(pluginName) {
|
|
943
|
-
return this.#resolvers.get(pluginName) ?? this.plugins.get(pluginName)?.resolver ?? this.#
|
|
1355
|
+
return this.#resolvers.get(pluginName) ?? this.plugins.get(pluginName)?.resolver ?? this.#getDefaultResolver(pluginName);
|
|
944
1356
|
}
|
|
945
1357
|
getContext(plugin) {
|
|
946
1358
|
const driver = this;
|
|
@@ -950,7 +1362,7 @@ var PluginDriver = class PluginDriver {
|
|
|
950
1362
|
return resolve(driver.config.root, driver.config.output.path);
|
|
951
1363
|
},
|
|
952
1364
|
getMode(output) {
|
|
953
|
-
return
|
|
1365
|
+
return KubbDriver.getMode(resolve(driver.config.root, driver.config.output.path, output.path));
|
|
954
1366
|
},
|
|
955
1367
|
hooks: driver.hooks,
|
|
956
1368
|
plugin,
|
|
@@ -964,13 +1376,10 @@ var PluginDriver = class PluginDriver {
|
|
|
964
1376
|
upsertFile: async (...files) => {
|
|
965
1377
|
driver.fileManager.upsert(...files);
|
|
966
1378
|
},
|
|
967
|
-
get
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
schemas: [],
|
|
972
|
-
operations: [],
|
|
973
|
-
meta: driver.inputStreamNode?.meta
|
|
1379
|
+
get meta() {
|
|
1380
|
+
return driver.inputNode?.meta ?? {
|
|
1381
|
+
circularNames: [],
|
|
1382
|
+
enumNames: []
|
|
974
1383
|
};
|
|
975
1384
|
},
|
|
976
1385
|
get adapter() {
|
|
@@ -991,13 +1400,14 @@ var PluginDriver = class PluginDriver {
|
|
|
991
1400
|
info(message) {
|
|
992
1401
|
driver.hooks.emit("kubb:info", { message });
|
|
993
1402
|
},
|
|
994
|
-
openInStudio(options) {
|
|
995
|
-
if (!driver.config.devtools || driver.#
|
|
1403
|
+
async openInStudio(options) {
|
|
1404
|
+
if (!driver.config.devtools || driver.#studio.isOpen) return;
|
|
996
1405
|
if (typeof driver.config.devtools !== "object") throw new Error("Devtools must be an object");
|
|
997
|
-
if (!driver.
|
|
998
|
-
driver.#
|
|
1406
|
+
if (!driver.adapter || !driver.#studio.source) throw new Error("adapter is not defined, make sure you have set the parser in kubb.config.ts");
|
|
1407
|
+
driver.#studio.isOpen = true;
|
|
999
1408
|
const studioUrl = driver.config.devtools?.studioUrl ?? "https://kubb.studio";
|
|
1000
|
-
|
|
1409
|
+
driver.#studio.inputNode ??= Promise.resolve(driver.adapter.parse(driver.#studio.source));
|
|
1410
|
+
return openInStudio(await driver.#studio.inputNode, studioUrl, options);
|
|
1001
1411
|
}
|
|
1002
1412
|
};
|
|
1003
1413
|
}
|
|
@@ -1044,7 +1454,23 @@ async function applyAsyncRender({ renderer, result, driver }) {
|
|
|
1044
1454
|
driver.fileManager.upsert(...renderer.files);
|
|
1045
1455
|
renderer.unmount();
|
|
1046
1456
|
}
|
|
1457
|
+
function inputToAdapterSource(config) {
|
|
1458
|
+
const input = config.input;
|
|
1459
|
+
if (!input) throw new Error("[kubb] input is required when using an adapter. Provide input.path or input.data in your config.");
|
|
1460
|
+
if ("data" in input) return {
|
|
1461
|
+
type: "data",
|
|
1462
|
+
data: input.data
|
|
1463
|
+
};
|
|
1464
|
+
if (new URLPath(input.path).isURL) return {
|
|
1465
|
+
type: "path",
|
|
1466
|
+
path: input.path
|
|
1467
|
+
};
|
|
1468
|
+
return {
|
|
1469
|
+
type: "path",
|
|
1470
|
+
path: resolve(config.root, input.path)
|
|
1471
|
+
};
|
|
1472
|
+
}
|
|
1047
1473
|
//#endregion
|
|
1048
|
-
export { definePlugin as a, DEFAULT_STUDIO_URL as c, defineResolver as i, logLevel as l, applyHookResult as n, DEFAULT_BANNER as o, FileManager as r, DEFAULT_EXTENSION as s,
|
|
1474
|
+
export { definePlugin as a, DEFAULT_STUDIO_URL as c, forBatches as d, isPromise as f, defineResolver as i, logLevel as l, applyHookResult as n, DEFAULT_BANNER as o, withDrain as p, FileManager as r, DEFAULT_EXTENSION as s, KubbDriver as t, URLPath as u };
|
|
1049
1475
|
|
|
1050
|
-
//# sourceMappingURL=
|
|
1476
|
+
//# sourceMappingURL=KubbDriver-Cxii_rBp.js.map
|