@kubb/core 5.0.0-beta.2 → 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/README.md +8 -38
- package/dist/{PluginDriver-BXibeQk-.cjs → KubbDriver-BXSnJ3qM.cjs} +719 -164
- package/dist/KubbDriver-BXSnJ3qM.cjs.map +1 -0
- package/dist/{PluginDriver-DV3p2Hky.js → KubbDriver-Cxii_rBp.js} +693 -162
- package/dist/KubbDriver-Cxii_rBp.js.map +1 -0
- package/dist/{types-CC09VtBt.d.ts → createKubb-Dcmtjqds.d.ts} +1395 -1238
- package/dist/index.cjs +556 -785
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +2 -185
- package/dist/index.js +551 -783
- package/dist/index.js.map +1 -1
- package/dist/mocks.cjs +30 -21
- package/dist/mocks.cjs.map +1 -1
- package/dist/mocks.d.ts +5 -5
- package/dist/mocks.js +29 -20
- package/dist/mocks.js.map +1 -1
- package/package.json +6 -18
- package/src/FileManager.ts +12 -0
- package/src/FileProcessor.ts +37 -38
- package/src/{PluginDriver.ts → KubbDriver.ts} +249 -86
- package/src/constants.ts +11 -6
- package/src/createAdapter.ts +84 -1
- package/src/createKubb.ts +1336 -297
- package/src/createRenderer.ts +23 -22
- package/src/defineGenerator.ts +96 -7
- package/src/defineLogger.ts +42 -3
- package/src/defineMiddleware.ts +1 -1
- package/src/defineParser.ts +1 -1
- package/src/definePlugin.ts +304 -8
- package/src/defineResolver.ts +268 -147
- package/src/devtools.ts +8 -1
- package/src/index.ts +2 -2
- package/src/mocks.ts +11 -14
- package/src/storages/fsStorage.ts +13 -37
- package/src/types.ts +38 -1292
- package/dist/PluginDriver-BXibeQk-.cjs.map +0 -1
- package/dist/PluginDriver-DV3p2Hky.js.map +0 -1
- package/src/Kubb.ts +0 -300
- 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
|
@@ -93,11 +93,386 @@ function pascalCase(text, { isFile, prefix = "", suffix = "" } = {}) {
|
|
|
93
93
|
return toCamelOrPascal(`${prefix} ${text} ${suffix}`, true);
|
|
94
94
|
}
|
|
95
95
|
//#endregion
|
|
96
|
+
//#region ../../internals/utils/src/promise.ts
|
|
97
|
+
function* chunks(arr, size) {
|
|
98
|
+
for (let i = 0; i < arr.length; i += size) yield arr.slice(i, i + size);
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Slices `source` into batches of `concurrency` items and awaits `process` for each batch.
|
|
102
|
+
* Accepts both plain arrays (sync) and `AsyncIterable` (streaming).
|
|
103
|
+
*
|
|
104
|
+
* `process` controls whether items inside a batch run in parallel; this helper only
|
|
105
|
+
* controls batch size and per-batch flushing.
|
|
106
|
+
*
|
|
107
|
+
* @example
|
|
108
|
+
* ```ts
|
|
109
|
+
* // parallel dispatch inside each batch
|
|
110
|
+
* await forBatches(schemas, (batch) => Promise.all(batch.map(process)), { concurrency: 8 })
|
|
111
|
+
*
|
|
112
|
+
* // async iterable with a flush after every batch
|
|
113
|
+
* await forBatches(stream.schemas, (batch) => dispatch(batch), { concurrency: 8, flush })
|
|
114
|
+
* ```
|
|
115
|
+
*/
|
|
116
|
+
async function forBatches(source, process, options) {
|
|
117
|
+
const { concurrency, flush } = options;
|
|
118
|
+
if (Array.isArray(source)) {
|
|
119
|
+
for (const batch of chunks(source, concurrency)) {
|
|
120
|
+
await process(batch);
|
|
121
|
+
if (flush) await flush();
|
|
122
|
+
}
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
const batch = [];
|
|
126
|
+
for await (const item of source) {
|
|
127
|
+
batch.push(item);
|
|
128
|
+
if (batch.length >= concurrency) {
|
|
129
|
+
await process(batch.splice(0));
|
|
130
|
+
if (flush) await flush();
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
if (batch.length > 0) {
|
|
134
|
+
await process(batch.splice(0));
|
|
135
|
+
if (flush) await flush();
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Runs `work`, passing `flush` as its periodic-flush callback, then calls
|
|
140
|
+
* `flush` once more to drain any items that did not cross a flush boundary.
|
|
141
|
+
*
|
|
142
|
+
* @example
|
|
143
|
+
* ```ts
|
|
144
|
+
* await withDrain(
|
|
145
|
+
* (flush) => processItems(items, { flush }),
|
|
146
|
+
* () => writeRemainingFiles(),
|
|
147
|
+
* )
|
|
148
|
+
* ```
|
|
149
|
+
*/
|
|
150
|
+
async function withDrain(work, flush) {
|
|
151
|
+
await work(flush);
|
|
152
|
+
await flush();
|
|
153
|
+
}
|
|
154
|
+
/** Returns `true` when `result` is a thenable `Promise`.
|
|
155
|
+
*
|
|
156
|
+
* @example
|
|
157
|
+
* ```ts
|
|
158
|
+
* isPromise(Promise.resolve(1)) // true
|
|
159
|
+
* isPromise(42) // false
|
|
160
|
+
* ```
|
|
161
|
+
*/
|
|
162
|
+
function isPromise(result) {
|
|
163
|
+
return result !== null && result !== void 0 && typeof result["then"] === "function";
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Wraps `factory` with a keyed cache backed by the provided store.
|
|
167
|
+
*
|
|
168
|
+
* Pass a `WeakMap` for object keys (results are GC-eligible when the key is
|
|
169
|
+
* collected) or a `Map` for primitive keys. For multi-argument functions,
|
|
170
|
+
* nest two `memoize` calls — the outer keyed by the first argument, the
|
|
171
|
+
* inner (created once per outer miss) keyed by the second.
|
|
172
|
+
*
|
|
173
|
+
* Because the cache is owned by the caller, it can be shared, inspected, or
|
|
174
|
+
* cleared independently of the memoized function.
|
|
175
|
+
*
|
|
176
|
+
* @example Single WeakMap key
|
|
177
|
+
* ```ts
|
|
178
|
+
* const cache = new WeakMap<SchemaNode, Set<string>>()
|
|
179
|
+
* const getRefs = memoize(cache, (node) => collectRefs(node))
|
|
180
|
+
* ```
|
|
181
|
+
*
|
|
182
|
+
* @example Single Map key (primitive)
|
|
183
|
+
* ```ts
|
|
184
|
+
* const cache = new Map<string, Resolver>()
|
|
185
|
+
* const getResolver = memoize(cache, (name) => buildResolver(name))
|
|
186
|
+
* ```
|
|
187
|
+
*
|
|
188
|
+
* @example Two-level (object + primitive)
|
|
189
|
+
* ```ts
|
|
190
|
+
* const outer = new WeakMap<Params[], Map<string, Params[]>>()
|
|
191
|
+
* const fn = memoize(outer, (params) => memoize(new Map(), (key) => transform(params, key)))
|
|
192
|
+
* fn(params)('camelcase')
|
|
193
|
+
* ```
|
|
194
|
+
*/
|
|
195
|
+
function memoize(store, factory) {
|
|
196
|
+
return (key) => {
|
|
197
|
+
if (store.has(key)) return store.get(key);
|
|
198
|
+
const value = factory(key);
|
|
199
|
+
store.set(key, value);
|
|
200
|
+
return value;
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* Wraps a plain array in a reusable `AsyncIterable`.
|
|
205
|
+
* Each `[Symbol.asyncIterator]()` call returns a fresh generator so the
|
|
206
|
+
* iterable can be consumed multiple times (e.g. once per plugin pre-scan).
|
|
207
|
+
*
|
|
208
|
+
* @example
|
|
209
|
+
* ```ts
|
|
210
|
+
* const stream = arrayToAsyncIterable([1, 2, 3])
|
|
211
|
+
* for await (const n of stream) console.log(n) // 1, 2, 3
|
|
212
|
+
* ```
|
|
213
|
+
*/
|
|
214
|
+
function arrayToAsyncIterable(arr) {
|
|
215
|
+
return { [Symbol.asyncIterator]() {
|
|
216
|
+
return (async function* () {
|
|
217
|
+
yield* arr;
|
|
218
|
+
})();
|
|
219
|
+
} };
|
|
220
|
+
}
|
|
221
|
+
//#endregion
|
|
222
|
+
//#region ../../internals/utils/src/reserved.ts
|
|
223
|
+
/**
|
|
224
|
+
* JavaScript and Java reserved words.
|
|
225
|
+
* @link https://github.com/jonschlinkert/reserved/blob/master/index.js
|
|
226
|
+
*/
|
|
227
|
+
const reservedWords = new Set([
|
|
228
|
+
"abstract",
|
|
229
|
+
"arguments",
|
|
230
|
+
"boolean",
|
|
231
|
+
"break",
|
|
232
|
+
"byte",
|
|
233
|
+
"case",
|
|
234
|
+
"catch",
|
|
235
|
+
"char",
|
|
236
|
+
"class",
|
|
237
|
+
"const",
|
|
238
|
+
"continue",
|
|
239
|
+
"debugger",
|
|
240
|
+
"default",
|
|
241
|
+
"delete",
|
|
242
|
+
"do",
|
|
243
|
+
"double",
|
|
244
|
+
"else",
|
|
245
|
+
"enum",
|
|
246
|
+
"eval",
|
|
247
|
+
"export",
|
|
248
|
+
"extends",
|
|
249
|
+
"false",
|
|
250
|
+
"final",
|
|
251
|
+
"finally",
|
|
252
|
+
"float",
|
|
253
|
+
"for",
|
|
254
|
+
"function",
|
|
255
|
+
"goto",
|
|
256
|
+
"if",
|
|
257
|
+
"implements",
|
|
258
|
+
"import",
|
|
259
|
+
"in",
|
|
260
|
+
"instanceof",
|
|
261
|
+
"int",
|
|
262
|
+
"interface",
|
|
263
|
+
"let",
|
|
264
|
+
"long",
|
|
265
|
+
"native",
|
|
266
|
+
"new",
|
|
267
|
+
"null",
|
|
268
|
+
"package",
|
|
269
|
+
"private",
|
|
270
|
+
"protected",
|
|
271
|
+
"public",
|
|
272
|
+
"return",
|
|
273
|
+
"short",
|
|
274
|
+
"static",
|
|
275
|
+
"super",
|
|
276
|
+
"switch",
|
|
277
|
+
"synchronized",
|
|
278
|
+
"this",
|
|
279
|
+
"throw",
|
|
280
|
+
"throws",
|
|
281
|
+
"transient",
|
|
282
|
+
"true",
|
|
283
|
+
"try",
|
|
284
|
+
"typeof",
|
|
285
|
+
"var",
|
|
286
|
+
"void",
|
|
287
|
+
"volatile",
|
|
288
|
+
"while",
|
|
289
|
+
"with",
|
|
290
|
+
"yield",
|
|
291
|
+
"Array",
|
|
292
|
+
"Date",
|
|
293
|
+
"hasOwnProperty",
|
|
294
|
+
"Infinity",
|
|
295
|
+
"isFinite",
|
|
296
|
+
"isNaN",
|
|
297
|
+
"isPrototypeOf",
|
|
298
|
+
"length",
|
|
299
|
+
"Math",
|
|
300
|
+
"name",
|
|
301
|
+
"NaN",
|
|
302
|
+
"Number",
|
|
303
|
+
"Object",
|
|
304
|
+
"prototype",
|
|
305
|
+
"String",
|
|
306
|
+
"toString",
|
|
307
|
+
"undefined",
|
|
308
|
+
"valueOf"
|
|
309
|
+
]);
|
|
310
|
+
/**
|
|
311
|
+
* Returns `true` when `name` is a syntactically valid JavaScript variable name.
|
|
312
|
+
*
|
|
313
|
+
* @example
|
|
314
|
+
* ```ts
|
|
315
|
+
* isValidVarName('status') // true
|
|
316
|
+
* isValidVarName('class') // false (reserved word)
|
|
317
|
+
* isValidVarName('42foo') // false (starts with digit)
|
|
318
|
+
* ```
|
|
319
|
+
*/
|
|
320
|
+
function isValidVarName(name) {
|
|
321
|
+
if (!name || reservedWords.has(name)) return false;
|
|
322
|
+
return /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(name);
|
|
323
|
+
}
|
|
324
|
+
//#endregion
|
|
325
|
+
//#region ../../internals/utils/src/urlPath.ts
|
|
326
|
+
/**
|
|
327
|
+
* Parses and transforms an OpenAPI/Swagger path string into various URL formats.
|
|
328
|
+
*
|
|
329
|
+
* @example
|
|
330
|
+
* const p = new URLPath('/pet/{petId}')
|
|
331
|
+
* p.URL // '/pet/:petId'
|
|
332
|
+
* p.template // '`/pet/${petId}`'
|
|
333
|
+
*/
|
|
334
|
+
var URLPath = class {
|
|
335
|
+
/**
|
|
336
|
+
* The raw OpenAPI/Swagger path string, e.g. `/pet/{petId}`.
|
|
337
|
+
*/
|
|
338
|
+
path;
|
|
339
|
+
#options;
|
|
340
|
+
constructor(path, options = {}) {
|
|
341
|
+
this.path = path;
|
|
342
|
+
this.#options = options;
|
|
343
|
+
}
|
|
344
|
+
/** Converts the OpenAPI path to Express-style colon syntax, e.g. `/pet/{petId}` → `/pet/:petId`.
|
|
345
|
+
*
|
|
346
|
+
* @example
|
|
347
|
+
* ```ts
|
|
348
|
+
* new URLPath('/pet/{petId}').URL // '/pet/:petId'
|
|
349
|
+
* ```
|
|
350
|
+
*/
|
|
351
|
+
get URL() {
|
|
352
|
+
return this.toURLPath();
|
|
353
|
+
}
|
|
354
|
+
/** Returns `true` when `path` is a fully-qualified URL (e.g. starts with `https://`).
|
|
355
|
+
*
|
|
356
|
+
* @example
|
|
357
|
+
* ```ts
|
|
358
|
+
* new URLPath('https://petstore.swagger.io/v2/pet').isURL // true
|
|
359
|
+
* new URLPath('/pet/{petId}').isURL // false
|
|
360
|
+
* ```
|
|
361
|
+
*/
|
|
362
|
+
get isURL() {
|
|
363
|
+
try {
|
|
364
|
+
return !!new URL(this.path).href;
|
|
365
|
+
} catch {
|
|
366
|
+
return false;
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
/**
|
|
370
|
+
* Converts the OpenAPI path to a TypeScript template literal string.
|
|
371
|
+
*
|
|
372
|
+
* @example
|
|
373
|
+
* new URLPath('/pet/{petId}').template // '`/pet/${petId}`'
|
|
374
|
+
* new URLPath('/account/monetary-accountID').template // '`/account/${monetaryAccountId}`'
|
|
375
|
+
*/
|
|
376
|
+
get template() {
|
|
377
|
+
return this.toTemplateString();
|
|
378
|
+
}
|
|
379
|
+
/** Returns the path and its extracted params as a structured `URLObject`, or as a stringified expression when `stringify` is set.
|
|
380
|
+
*
|
|
381
|
+
* @example
|
|
382
|
+
* ```ts
|
|
383
|
+
* new URLPath('/pet/{petId}').object
|
|
384
|
+
* // { url: '/pet/:petId', params: { petId: 'petId' } }
|
|
385
|
+
* ```
|
|
386
|
+
*/
|
|
387
|
+
get object() {
|
|
388
|
+
return this.toObject();
|
|
389
|
+
}
|
|
390
|
+
/** Returns a map of path parameter names, or `undefined` when the path has no parameters.
|
|
391
|
+
*
|
|
392
|
+
* @example
|
|
393
|
+
* ```ts
|
|
394
|
+
* new URLPath('/pet/{petId}').params // { petId: 'petId' }
|
|
395
|
+
* new URLPath('/pet').params // undefined
|
|
396
|
+
* ```
|
|
397
|
+
*/
|
|
398
|
+
get params() {
|
|
399
|
+
return this.getParams();
|
|
400
|
+
}
|
|
401
|
+
#transformParam(raw) {
|
|
402
|
+
const param = isValidVarName(raw) ? raw : camelCase(raw);
|
|
403
|
+
return this.#options.casing === "camelcase" ? camelCase(param) : param;
|
|
404
|
+
}
|
|
405
|
+
/**
|
|
406
|
+
* Iterates over every `{param}` token in `path`, calling `fn` with the raw token and transformed name.
|
|
407
|
+
*/
|
|
408
|
+
#eachParam(fn) {
|
|
409
|
+
for (const match of this.path.matchAll(/\{([^}]+)\}/g)) {
|
|
410
|
+
const raw = match[1];
|
|
411
|
+
fn(raw, this.#transformParam(raw));
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
toObject({ type = "path", replacer, stringify } = {}) {
|
|
415
|
+
const object = {
|
|
416
|
+
url: type === "path" ? this.toURLPath() : this.toTemplateString({ replacer }),
|
|
417
|
+
params: this.getParams()
|
|
418
|
+
};
|
|
419
|
+
if (stringify) {
|
|
420
|
+
if (type === "template") return JSON.stringify(object).replaceAll("'", "").replaceAll(`"`, "");
|
|
421
|
+
if (object.params) return `{ url: '${object.url}', params: ${JSON.stringify(object.params).replaceAll("'", "").replaceAll(`"`, "")} }`;
|
|
422
|
+
return `{ url: '${object.url}' }`;
|
|
423
|
+
}
|
|
424
|
+
return object;
|
|
425
|
+
}
|
|
426
|
+
/**
|
|
427
|
+
* Converts the OpenAPI path to a TypeScript template literal string.
|
|
428
|
+
* An optional `replacer` can transform each extracted parameter name before interpolation.
|
|
429
|
+
*
|
|
430
|
+
* @example
|
|
431
|
+
* new URLPath('/pet/{petId}').toTemplateString() // '`/pet/${petId}`'
|
|
432
|
+
*/
|
|
433
|
+
toTemplateString({ prefix = "", replacer } = {}) {
|
|
434
|
+
return `\`${prefix}${this.path.split(/\{([^}]+)\}/).map((part, i) => {
|
|
435
|
+
if (i % 2 === 0) return part;
|
|
436
|
+
const param = this.#transformParam(part);
|
|
437
|
+
return `\${${replacer ? replacer(param) : param}}`;
|
|
438
|
+
}).join("")}\``;
|
|
439
|
+
}
|
|
440
|
+
/**
|
|
441
|
+
* Extracts all `{param}` segments from the path and returns them as a key-value map.
|
|
442
|
+
* An optional `replacer` transforms each parameter name in both key and value positions.
|
|
443
|
+
* Returns `undefined` when no path parameters are found.
|
|
444
|
+
*
|
|
445
|
+
* @example
|
|
446
|
+
* ```ts
|
|
447
|
+
* new URLPath('/pet/{petId}/tag/{tagId}').getParams()
|
|
448
|
+
* // { petId: 'petId', tagId: 'tagId' }
|
|
449
|
+
* ```
|
|
450
|
+
*/
|
|
451
|
+
getParams(replacer) {
|
|
452
|
+
const params = {};
|
|
453
|
+
this.#eachParam((_raw, param) => {
|
|
454
|
+
const key = replacer ? replacer(param) : param;
|
|
455
|
+
params[key] = key;
|
|
456
|
+
});
|
|
457
|
+
return Object.keys(params).length > 0 ? params : void 0;
|
|
458
|
+
}
|
|
459
|
+
/** Converts the OpenAPI path to Express-style colon syntax.
|
|
460
|
+
*
|
|
461
|
+
* @example
|
|
462
|
+
* ```ts
|
|
463
|
+
* new URLPath('/pet/{petId}').toURLPath() // '/pet/:petId'
|
|
464
|
+
* ```
|
|
465
|
+
*/
|
|
466
|
+
toURLPath() {
|
|
467
|
+
return this.path.replace(/\{([^}]+)\}/g, ":$1");
|
|
468
|
+
}
|
|
469
|
+
};
|
|
470
|
+
//#endregion
|
|
96
471
|
//#region src/constants.ts
|
|
97
472
|
/**
|
|
98
473
|
* Base URL for the Kubb Studio web app.
|
|
99
474
|
*/
|
|
100
|
-
const DEFAULT_STUDIO_URL = "https://
|
|
475
|
+
const DEFAULT_STUDIO_URL = "https://kubb.studio";
|
|
101
476
|
/**
|
|
102
477
|
* Default banner style written at the top of every generated file.
|
|
103
478
|
*/
|
|
@@ -120,6 +495,42 @@ const logLevel = {
|
|
|
120
495
|
debug: 5
|
|
121
496
|
};
|
|
122
497
|
//#endregion
|
|
498
|
+
//#region src/definePlugin.ts
|
|
499
|
+
/**
|
|
500
|
+
* Wraps a factory function and returns a typed `Plugin` with lifecycle handlers grouped under `hooks`.
|
|
501
|
+
*
|
|
502
|
+
* Handlers live in a single `hooks` object (inspired by Astro integrations).
|
|
503
|
+
* All lifecycle events from `KubbHooks` are available for subscription.
|
|
504
|
+
*
|
|
505
|
+
* @note For real plugins, use a `PluginFactoryOptions` type parameter to get type-safe context in `kubb:plugin:setup`.
|
|
506
|
+
* Plugin names should follow the convention `plugin-<feature>` (e.g., `plugin-react-query`, `plugin-zod`).
|
|
507
|
+
*
|
|
508
|
+
* @example
|
|
509
|
+
* ```ts
|
|
510
|
+
* import { definePlugin } from '@kubb/core'
|
|
511
|
+
*
|
|
512
|
+
* export const pluginTs = definePlugin((options: { prefix?: string } = {}) => ({
|
|
513
|
+
* name: 'plugin-ts',
|
|
514
|
+
* hooks: {
|
|
515
|
+
* 'kubb:plugin:setup'(ctx) {
|
|
516
|
+
* ctx.setResolver(resolverTs)
|
|
517
|
+
* },
|
|
518
|
+
* },
|
|
519
|
+
* }))
|
|
520
|
+
* ```
|
|
521
|
+
*/
|
|
522
|
+
function definePlugin(factory) {
|
|
523
|
+
return (options) => factory(options ?? {});
|
|
524
|
+
}
|
|
525
|
+
/**
|
|
526
|
+
* Returns `'single'` when `fileOrFolder` has a file extension, `'split'` otherwise.
|
|
527
|
+
* Used to determine whether an output path targets a single file or a directory.
|
|
528
|
+
*/
|
|
529
|
+
function getMode(fileOrFolder) {
|
|
530
|
+
if (!fileOrFolder) return "split";
|
|
531
|
+
return (0, node_path.extname)(fileOrFolder) ? "single" : "split";
|
|
532
|
+
}
|
|
533
|
+
//#endregion
|
|
123
534
|
//#region src/defineResolver.ts
|
|
124
535
|
const stringPatternCache = /* @__PURE__ */ new Map();
|
|
125
536
|
function testPattern(value, pattern) {
|
|
@@ -137,14 +548,12 @@ function testPattern(value, pattern) {
|
|
|
137
548
|
* Checks if an operation matches a pattern for a given filter type (`tag`, `operationId`, `path`, `method`).
|
|
138
549
|
*/
|
|
139
550
|
function matchesOperationPattern(node, type, pattern) {
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
default: return false;
|
|
147
|
-
}
|
|
551
|
+
if (type === "tag") return node.tags.some((tag) => testPattern(tag, pattern));
|
|
552
|
+
if (type === "operationId") return testPattern(node.operationId, pattern);
|
|
553
|
+
if (type === "path") return testPattern(node.path, pattern);
|
|
554
|
+
if (type === "method") return testPattern(node.method.toLowerCase(), pattern);
|
|
555
|
+
if (type === "contentType") return node.requestBody?.content?.some((c) => testPattern(c.contentType, pattern)) ?? false;
|
|
556
|
+
return false;
|
|
148
557
|
}
|
|
149
558
|
/**
|
|
150
559
|
* Checks if a schema matches a pattern for a given filter type (`schemaName`).
|
|
@@ -152,10 +561,8 @@ function matchesOperationPattern(node, type, pattern) {
|
|
|
152
561
|
* Returns `null` when the filter type doesn't apply to schemas.
|
|
153
562
|
*/
|
|
154
563
|
function matchesSchemaPattern(node, type, pattern) {
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
default: return null;
|
|
158
|
-
}
|
|
564
|
+
if (type === "schemaName") return node.name ? testPattern(node.name, pattern) : false;
|
|
565
|
+
return null;
|
|
159
566
|
}
|
|
160
567
|
/**
|
|
161
568
|
* Default name resolver used by `defineResolver`.
|
|
@@ -165,10 +572,9 @@ function matchesSchemaPattern(node, type, pattern) {
|
|
|
165
572
|
* - `camelCase` for everything else.
|
|
166
573
|
*/
|
|
167
574
|
function defaultResolver(name, type) {
|
|
168
|
-
|
|
169
|
-
if (type === "
|
|
170
|
-
|
|
171
|
-
return resolvedName;
|
|
575
|
+
if (type === "file" || type === "function") return camelCase(name, { isFile: type === "file" });
|
|
576
|
+
if (type === "type") return pascalCase(name);
|
|
577
|
+
return camelCase(name);
|
|
172
578
|
}
|
|
173
579
|
/**
|
|
174
580
|
* Default option resolver — applies include/exclude filters and merges matching override options.
|
|
@@ -193,7 +599,8 @@ function defaultResolver(name, type) {
|
|
|
193
599
|
* // → { enumType: 'enum' } when operationId matches
|
|
194
600
|
* ```
|
|
195
601
|
*/
|
|
196
|
-
|
|
602
|
+
const resolveOptionsCache = /* @__PURE__ */ new WeakMap();
|
|
603
|
+
function computeOptions(node, options, exclude, include, override) {
|
|
197
604
|
if ((0, _kubb_ast.isOperationNode)(node)) {
|
|
198
605
|
if (exclude.some(({ type, pattern }) => matchesOperationPattern(node, type, pattern))) return null;
|
|
199
606
|
if (include && !include.some(({ type, pattern }) => matchesOperationPattern(node, type, pattern))) return null;
|
|
@@ -217,6 +624,19 @@ function defaultResolveOptions(node, { options, exclude = [], include, override
|
|
|
217
624
|
}
|
|
218
625
|
return options;
|
|
219
626
|
}
|
|
627
|
+
function defaultResolveOptions(node, { options, exclude = [], include, override = [] }) {
|
|
628
|
+
const optionsKey = options;
|
|
629
|
+
let byOptions = resolveOptionsCache.get(optionsKey);
|
|
630
|
+
if (!byOptions) {
|
|
631
|
+
byOptions = /* @__PURE__ */ new WeakMap();
|
|
632
|
+
resolveOptionsCache.set(optionsKey, byOptions);
|
|
633
|
+
}
|
|
634
|
+
const cached = byOptions.get(node);
|
|
635
|
+
if (cached !== void 0) return cached.value;
|
|
636
|
+
const result = computeOptions(node, options, exclude, include, override);
|
|
637
|
+
byOptions.set(node, { value: result });
|
|
638
|
+
return result;
|
|
639
|
+
}
|
|
220
640
|
/**
|
|
221
641
|
* Default path resolver used by `defineResolver`.
|
|
222
642
|
*
|
|
@@ -262,17 +682,19 @@ function defaultResolveOptions(node, { options, exclude = [], include, override
|
|
|
262
682
|
* ```
|
|
263
683
|
*/
|
|
264
684
|
function defaultResolvePath({ baseName, pathMode, tag, path: groupPath }, { root, output, group }) {
|
|
265
|
-
if ((pathMode ??
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
685
|
+
if ((pathMode ?? getMode(node_path.default.resolve(root, output.path))) === "single") return node_path.default.resolve(root, output.path);
|
|
686
|
+
const result = (() => {
|
|
687
|
+
if (group && (groupPath || tag)) {
|
|
688
|
+
const groupValue = group.type === "path" ? groupPath : tag;
|
|
689
|
+
const defaultName = group.type === "tag" ? ({ group: g }) => `${camelCase(g)}Controller` : ({ group: g }) => {
|
|
690
|
+
const segment = g.split("/").filter((s) => s !== "" && s !== "." && s !== "..")[0];
|
|
691
|
+
return segment ? camelCase(segment) : "";
|
|
692
|
+
};
|
|
693
|
+
const resolveName = group.name ?? defaultName;
|
|
694
|
+
return node_path.default.resolve(root, output.path, resolveName({ group: groupValue }), baseName);
|
|
695
|
+
}
|
|
696
|
+
return node_path.default.resolve(root, output.path, baseName);
|
|
697
|
+
})();
|
|
276
698
|
const outputDir = node_path.default.resolve(root, output.path);
|
|
277
699
|
const outputDirWithSep = outputDir.endsWith(node_path.default.sep) ? outputDir : `${outputDir}${node_path.default.sep}`;
|
|
278
700
|
if (result !== outputDir && !result.startsWith(outputDirWithSep)) throw new Error(`[Kubb] Resolved path "${result}" is outside the output directory "${outputDir}". This may indicate a path traversal attempt in the OpenAPI specification or a misconfigured group.name function.`);
|
|
@@ -289,28 +711,28 @@ function defaultResolvePath({ baseName, pathMode, tag, path: groupPath }, { root
|
|
|
289
711
|
*
|
|
290
712
|
* @example Resolve a schema file
|
|
291
713
|
* ```ts
|
|
292
|
-
* const file = defaultResolveFile(
|
|
714
|
+
* const file = defaultResolveFile.call(
|
|
715
|
+
* resolver,
|
|
293
716
|
* { name: 'pet', extname: '.ts' },
|
|
294
717
|
* { root: '/src', output: { path: 'types' } },
|
|
295
|
-
* resolver,
|
|
296
718
|
* )
|
|
297
719
|
* // → { baseName: 'pet.ts', path: '/src/types/pet.ts', sources: [], ... }
|
|
298
720
|
* ```
|
|
299
721
|
*
|
|
300
722
|
* @example Resolve an operation file with tag grouping
|
|
301
723
|
* ```ts
|
|
302
|
-
* const file = defaultResolveFile(
|
|
724
|
+
* const file = defaultResolveFile.call(
|
|
725
|
+
* resolver,
|
|
303
726
|
* { name: 'listPets', extname: '.ts', tag: 'pets' },
|
|
304
727
|
* { root: '/src', output: { path: 'types' }, group: { type: 'tag' } },
|
|
305
|
-
* resolver,
|
|
306
728
|
* )
|
|
307
729
|
* // → { baseName: 'listPets.ts', path: '/src/types/petsController/listPets.ts', ... }
|
|
308
730
|
* ```
|
|
309
731
|
*/
|
|
310
|
-
function defaultResolveFile({ name, extname, tag, path: groupPath }, context
|
|
311
|
-
const pathMode =
|
|
312
|
-
const baseName = `${pathMode === "single" ? "" :
|
|
313
|
-
const filePath =
|
|
732
|
+
function defaultResolveFile({ name, extname, tag, path: groupPath }, context) {
|
|
733
|
+
const pathMode = getMode(node_path.default.resolve(context.root, context.output.path));
|
|
734
|
+
const baseName = `${pathMode === "single" ? "" : this.default(name, "file")}${extname}`;
|
|
735
|
+
const filePath = this.resolvePath({
|
|
314
736
|
baseName,
|
|
315
737
|
pathMode,
|
|
316
738
|
tag,
|
|
@@ -319,7 +741,7 @@ function defaultResolveFile({ name, extname, tag, path: groupPath }, context, ct
|
|
|
319
741
|
return (0, _kubb_ast.createFile)({
|
|
320
742
|
path: filePath,
|
|
321
743
|
baseName: node_path.default.basename(filePath),
|
|
322
|
-
meta: { pluginName:
|
|
744
|
+
meta: { pluginName: this.pluginName },
|
|
323
745
|
sources: [],
|
|
324
746
|
imports: [],
|
|
325
747
|
exports: []
|
|
@@ -330,12 +752,16 @@ function defaultResolveFile({ name, extname, tag, path: groupPath }, context, ct
|
|
|
330
752
|
*/
|
|
331
753
|
function buildDefaultBanner({ title, description, version, config }) {
|
|
332
754
|
try {
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
755
|
+
const source = (() => {
|
|
756
|
+
if (Array.isArray(config.input)) {
|
|
757
|
+
const first = config.input[0];
|
|
758
|
+
if (first && "path" in first) return node_path.default.basename(first.path);
|
|
759
|
+
return "";
|
|
760
|
+
}
|
|
761
|
+
if (config.input && "path" in config.input) return node_path.default.basename(config.input.path);
|
|
762
|
+
if (config.input && "data" in config.input) return "text content";
|
|
763
|
+
return "";
|
|
764
|
+
})();
|
|
339
765
|
let banner = "/**\n* Generated by Kubb (https://kubb.dev/).\n* Do not edit manually.\n";
|
|
340
766
|
if (config.output.defaultBanner === "simple") {
|
|
341
767
|
banner += "*/\n";
|
|
@@ -359,10 +785,9 @@ function buildDefaultBanner({ title, description, version, config }) {
|
|
|
359
785
|
*
|
|
360
786
|
* A user-supplied `output.banner` overrides the default Kubb "Generated by Kubb" notice.
|
|
361
787
|
* When no `output.banner` is set, the Kubb notice is used (including `title` and `version`
|
|
362
|
-
* from the
|
|
788
|
+
* from the document metadata when `meta` is provided).
|
|
363
789
|
*
|
|
364
|
-
* - When `output.banner` is a function
|
|
365
|
-
* - When `output.banner` is a function and `node` is absent, falls back to the Kubb notice.
|
|
790
|
+
* - When `output.banner` is a function, calls it with `meta` and returns the result.
|
|
366
791
|
* - When `output.banner` is a string, returns it directly.
|
|
367
792
|
* - When `config.output.defaultBanner` is `false`, returns `undefined`.
|
|
368
793
|
* - Otherwise returns the Kubb "Generated by Kubb" notice.
|
|
@@ -373,15 +798,15 @@ function buildDefaultBanner({ title, description, version, config }) {
|
|
|
373
798
|
* // → '// my banner'
|
|
374
799
|
* ```
|
|
375
800
|
*
|
|
376
|
-
* @example Function banner with
|
|
801
|
+
* @example Function banner with metadata
|
|
377
802
|
* ```ts
|
|
378
|
-
* defaultResolveBanner(
|
|
803
|
+
* defaultResolveBanner(meta, { output: { banner: (m) => `// v${m?.version}` }, config })
|
|
379
804
|
* // → '// v3.0.0'
|
|
380
805
|
* ```
|
|
381
806
|
*
|
|
382
807
|
* @example No user banner — Kubb notice with OAS metadata
|
|
383
808
|
* ```ts
|
|
384
|
-
* defaultResolveBanner(
|
|
809
|
+
* defaultResolveBanner(meta, { config })
|
|
385
810
|
* // → '/** Generated by Kubb ... Title: Pet Store ... *\/'
|
|
386
811
|
* ```
|
|
387
812
|
*
|
|
@@ -391,21 +816,20 @@ function buildDefaultBanner({ title, description, version, config }) {
|
|
|
391
816
|
* // → undefined
|
|
392
817
|
* ```
|
|
393
818
|
*/
|
|
394
|
-
function defaultResolveBanner(
|
|
395
|
-
if (typeof output?.banner === "function") return output.banner(
|
|
819
|
+
function defaultResolveBanner(meta, { output, config }) {
|
|
820
|
+
if (typeof output?.banner === "function") return output.banner(meta);
|
|
396
821
|
if (typeof output?.banner === "string") return output.banner;
|
|
397
822
|
if (config.output.defaultBanner === false) return;
|
|
398
823
|
return buildDefaultBanner({
|
|
399
|
-
title:
|
|
400
|
-
version:
|
|
824
|
+
title: meta?.title,
|
|
825
|
+
version: meta?.version,
|
|
401
826
|
config
|
|
402
827
|
});
|
|
403
828
|
}
|
|
404
829
|
/**
|
|
405
830
|
* Default footer resolver — returns the footer string for a generated file.
|
|
406
831
|
*
|
|
407
|
-
* - When `output.footer` is a function
|
|
408
|
-
* - When `output.footer` is a function and `node` is absent, returns `undefined`.
|
|
832
|
+
* - When `output.footer` is a function, calls it with `meta` and returns the result.
|
|
409
833
|
* - When `output.footer` is a string, returns it directly.
|
|
410
834
|
* - Otherwise returns `undefined`.
|
|
411
835
|
*
|
|
@@ -415,14 +839,14 @@ function defaultResolveBanner(node, { output, config }) {
|
|
|
415
839
|
* // → '// end of file'
|
|
416
840
|
* ```
|
|
417
841
|
*
|
|
418
|
-
* @example Function footer with
|
|
842
|
+
* @example Function footer with metadata
|
|
419
843
|
* ```ts
|
|
420
|
-
* defaultResolveFooter(
|
|
844
|
+
* defaultResolveFooter(meta, { output: { footer: (m) => `// ${m?.title}` }, config })
|
|
421
845
|
* // → '// Pet Store'
|
|
422
846
|
* ```
|
|
423
847
|
*/
|
|
424
|
-
function defaultResolveFooter(
|
|
425
|
-
if (typeof output?.footer === "function") return
|
|
848
|
+
function defaultResolveFooter(meta, { output }) {
|
|
849
|
+
if (typeof output?.footer === "function") return output.footer(meta);
|
|
426
850
|
if (typeof output?.footer === "string") return output.footer;
|
|
427
851
|
}
|
|
428
852
|
/**
|
|
@@ -435,25 +859,24 @@ function defaultResolveFooter(node, { output }) {
|
|
|
435
859
|
* - `resolvePath` — output path computation
|
|
436
860
|
* - `resolveFile` — full `FileNode` construction
|
|
437
861
|
*
|
|
438
|
-
*
|
|
439
|
-
* call sibling resolver methods using `ctx` instead of `this`.
|
|
862
|
+
* Methods in the returned object can call sibling resolver methods via `this`.
|
|
440
863
|
*
|
|
441
864
|
* @example Basic resolver with naming helpers
|
|
442
865
|
* ```ts
|
|
443
|
-
* export const resolver = defineResolver<PluginTs>((
|
|
866
|
+
* export const resolver = defineResolver<PluginTs>(() => ({
|
|
444
867
|
* name: 'default',
|
|
445
868
|
* resolveName(node) {
|
|
446
|
-
* return
|
|
869
|
+
* return this.default(node.name, 'function')
|
|
447
870
|
* },
|
|
448
871
|
* resolveTypedName(node) {
|
|
449
|
-
* return
|
|
872
|
+
* return this.default(node.name, 'type')
|
|
450
873
|
* },
|
|
451
874
|
* }))
|
|
452
875
|
* ```
|
|
453
876
|
*
|
|
454
877
|
* @example Override resolvePath for a custom output structure
|
|
455
878
|
* ```ts
|
|
456
|
-
* export const resolver = defineResolver<PluginTs>((
|
|
879
|
+
* export const resolver = defineResolver<PluginTs>(() => ({
|
|
457
880
|
* name: 'custom',
|
|
458
881
|
* resolvePath({ baseName }, { root, output }) {
|
|
459
882
|
* return path.resolve(root, output.path, 'generated', baseName)
|
|
@@ -461,27 +884,27 @@ function defaultResolveFooter(node, { output }) {
|
|
|
461
884
|
* }))
|
|
462
885
|
* ```
|
|
463
886
|
*
|
|
464
|
-
* @example Use
|
|
887
|
+
* @example Use this.default inside a helper
|
|
465
888
|
* ```ts
|
|
466
|
-
* export const resolver = defineResolver<PluginTs>((
|
|
889
|
+
* export const resolver = defineResolver<PluginTs>(() => ({
|
|
467
890
|
* name: 'default',
|
|
468
891
|
* resolveParamName(node, param) {
|
|
469
|
-
* return
|
|
892
|
+
* return this.default(`${node.operationId} ${param.in} ${param.name}`, 'type')
|
|
470
893
|
* },
|
|
471
894
|
* }))
|
|
472
895
|
* ```
|
|
473
896
|
*/
|
|
474
897
|
function defineResolver(build) {
|
|
475
|
-
|
|
476
|
-
|
|
898
|
+
let resolver;
|
|
899
|
+
resolver = {
|
|
477
900
|
default: defaultResolver,
|
|
478
901
|
resolveOptions: defaultResolveOptions,
|
|
479
902
|
resolvePath: defaultResolvePath,
|
|
480
|
-
resolveFile: (params, context) => defaultResolveFile(params, context
|
|
903
|
+
resolveFile: (params, context) => defaultResolveFile.call(resolver, params, context),
|
|
481
904
|
resolveBanner: defaultResolveBanner,
|
|
482
905
|
resolveFooter: defaultResolveFooter,
|
|
483
|
-
...build(
|
|
484
|
-
}
|
|
906
|
+
...build()
|
|
907
|
+
};
|
|
485
908
|
return resolver;
|
|
486
909
|
}
|
|
487
910
|
//#endregion
|
|
@@ -609,6 +1032,16 @@ var FileManager = class {
|
|
|
609
1032
|
this.#filesCache = null;
|
|
610
1033
|
}
|
|
611
1034
|
/**
|
|
1035
|
+
* Releases all stored files. Called by the core after `kubb:build:end` to
|
|
1036
|
+
* free the per-plugin FileNode caches for the rest of the process lifetime.
|
|
1037
|
+
*/
|
|
1038
|
+
dispose() {
|
|
1039
|
+
this.clear();
|
|
1040
|
+
}
|
|
1041
|
+
[Symbol.dispose]() {
|
|
1042
|
+
this.dispose();
|
|
1043
|
+
}
|
|
1044
|
+
/**
|
|
612
1045
|
* All stored files, sorted by path length (shorter paths first).
|
|
613
1046
|
*/
|
|
614
1047
|
get files() {
|
|
@@ -626,35 +1059,11 @@ var FileManager = class {
|
|
|
626
1059
|
}
|
|
627
1060
|
};
|
|
628
1061
|
//#endregion
|
|
629
|
-
//#region src/
|
|
630
|
-
/**
|
|
631
|
-
* Handles the return value of a plugin AST hook or generator method.
|
|
632
|
-
*
|
|
633
|
-
* - Renderer output → rendered via the provided `rendererFactory` (e.g. JSX), files stored in `driver.fileManager`
|
|
634
|
-
* - `Array<FileNode>` → added directly into `driver.fileManager`
|
|
635
|
-
* - `void` / `null` / `undefined` → no-op (plugin handled it via `this.upsertFile`)
|
|
636
|
-
*
|
|
637
|
-
* Pass a `rendererFactory` (e.g. `jsxRenderer` from `@kubb/renderer-jsx`) when the result
|
|
638
|
-
* may be a renderer element. Generators that only return `Array<FileNode>` do not need one.
|
|
639
|
-
*/
|
|
640
|
-
async function applyHookResult(result, driver, rendererFactory) {
|
|
641
|
-
if (!result) return;
|
|
642
|
-
if (Array.isArray(result)) {
|
|
643
|
-
driver.fileManager.upsert(...result);
|
|
644
|
-
return;
|
|
645
|
-
}
|
|
646
|
-
if (!rendererFactory) return;
|
|
647
|
-
const renderer = rendererFactory();
|
|
648
|
-
await renderer.render(result);
|
|
649
|
-
driver.fileManager.upsert(...renderer.files);
|
|
650
|
-
renderer.unmount();
|
|
651
|
-
}
|
|
652
|
-
//#endregion
|
|
653
|
-
//#region src/PluginDriver.ts
|
|
1062
|
+
//#region src/KubbDriver.ts
|
|
654
1063
|
function enforceOrder(enforce) {
|
|
655
1064
|
return enforce === "pre" ? -1 : enforce === "post" ? 1 : 0;
|
|
656
1065
|
}
|
|
657
|
-
var
|
|
1066
|
+
var KubbDriver = class KubbDriver {
|
|
658
1067
|
config;
|
|
659
1068
|
options;
|
|
660
1069
|
/**
|
|
@@ -662,21 +1071,34 @@ var PluginDriver = class PluginDriver {
|
|
|
662
1071
|
*
|
|
663
1072
|
* @example
|
|
664
1073
|
* ```ts
|
|
665
|
-
*
|
|
666
|
-
*
|
|
1074
|
+
* KubbDriver.getMode('src/gen/types.ts') // 'single'
|
|
1075
|
+
* KubbDriver.getMode('src/gen/types') // 'split'
|
|
667
1076
|
* ```
|
|
668
1077
|
*/
|
|
669
1078
|
static getMode(fileOrFolder) {
|
|
670
|
-
|
|
671
|
-
return (0, node_path.extname)(fileOrFolder) ? "single" : "split";
|
|
1079
|
+
return getMode(fileOrFolder);
|
|
672
1080
|
}
|
|
673
1081
|
/**
|
|
674
|
-
* The
|
|
675
|
-
*
|
|
1082
|
+
* The streaming `InputStreamNode` produced by the adapter.
|
|
1083
|
+
* Always set after adapter setup — parse-only adapters are wrapped automatically.
|
|
676
1084
|
*/
|
|
677
1085
|
inputNode = void 0;
|
|
678
1086
|
adapter = void 0;
|
|
679
|
-
|
|
1087
|
+
/**
|
|
1088
|
+
* Studio session state, kept together so `dispose()` can reset it atomically.
|
|
1089
|
+
*
|
|
1090
|
+
* - `source` holds the raw adapter source so `adapter.parse()` can be called lazily.
|
|
1091
|
+
* Intentionally outlives the build; cleared by `dispose()`.
|
|
1092
|
+
* - `isOpen` prevents opening the studio more than once per build.
|
|
1093
|
+
* - `inputNode` caches the parse promise so `adapter.parse()` is called at most once
|
|
1094
|
+
* per studio session, even when `openInStudio()` is called multiple times.
|
|
1095
|
+
*/
|
|
1096
|
+
#studio = {
|
|
1097
|
+
source: void 0,
|
|
1098
|
+
isOpen: false,
|
|
1099
|
+
inputNode: void 0
|
|
1100
|
+
};
|
|
1101
|
+
#middlewareListeners = [];
|
|
680
1102
|
/**
|
|
681
1103
|
* Central file store for all generated files.
|
|
682
1104
|
* Plugins should use `this.addFile()` / `this.upsertFile()` (via their context) to
|
|
@@ -688,23 +1110,29 @@ var PluginDriver = class PluginDriver {
|
|
|
688
1110
|
* Tracks which plugins have generators registered via `addGenerator()` (event-based path).
|
|
689
1111
|
* Used by the build loop to decide whether to emit generator events for a given plugin.
|
|
690
1112
|
*/
|
|
691
|
-
#
|
|
1113
|
+
#eventGeneratorPlugins = /* @__PURE__ */ new Set();
|
|
692
1114
|
#resolvers = /* @__PURE__ */ new Map();
|
|
693
1115
|
#defaultResolvers = /* @__PURE__ */ new Map();
|
|
694
1116
|
#hookListeners = /* @__PURE__ */ new Map();
|
|
695
1117
|
constructor(config, options) {
|
|
696
1118
|
this.config = config;
|
|
697
1119
|
this.options = options;
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
1120
|
+
this.adapter = config.adapter;
|
|
1121
|
+
}
|
|
1122
|
+
async setup() {
|
|
1123
|
+
const normalized = this.config.plugins.map((rawPlugin) => this.#normalizePlugin(rawPlugin));
|
|
1124
|
+
normalized.sort((a, b) => {
|
|
702
1125
|
if (b.dependencies?.includes(a.name)) return -1;
|
|
703
1126
|
if (a.dependencies?.includes(b.name)) return 1;
|
|
704
1127
|
return enforceOrder(a.enforce) - enforceOrder(b.enforce);
|
|
705
|
-
}).forEach((plugin) => {
|
|
706
|
-
this.plugins.set(plugin.name, plugin);
|
|
707
1128
|
});
|
|
1129
|
+
for (const plugin of normalized) {
|
|
1130
|
+
if (plugin.apply) plugin.apply(this.config);
|
|
1131
|
+
this.#registerPlugin(plugin);
|
|
1132
|
+
this.plugins.set(plugin.name, plugin);
|
|
1133
|
+
}
|
|
1134
|
+
if (this.config.middleware) for (const middleware of this.config.middleware) for (const event of Object.keys(middleware.hooks)) this.#registerMiddleware(event, middleware.hooks);
|
|
1135
|
+
if (this.config.adapter) await this.#registerAdapter(this.config.adapter);
|
|
708
1136
|
}
|
|
709
1137
|
get hooks() {
|
|
710
1138
|
return this.options.hooks;
|
|
@@ -713,19 +1141,48 @@ var PluginDriver = class PluginDriver {
|
|
|
713
1141
|
* Creates an `NormalizedPlugin` from a hook-style plugin and registers
|
|
714
1142
|
* its lifecycle handlers on the `AsyncEventEmitter`.
|
|
715
1143
|
*/
|
|
716
|
-
#normalizePlugin(
|
|
717
|
-
const
|
|
718
|
-
name:
|
|
719
|
-
dependencies:
|
|
720
|
-
enforce:
|
|
721
|
-
|
|
1144
|
+
#normalizePlugin(plugin) {
|
|
1145
|
+
const normalized = {
|
|
1146
|
+
name: plugin.name,
|
|
1147
|
+
dependencies: plugin.dependencies,
|
|
1148
|
+
enforce: plugin.enforce,
|
|
1149
|
+
hooks: plugin.hooks,
|
|
1150
|
+
options: plugin.options ?? {
|
|
722
1151
|
output: { path: "." },
|
|
723
1152
|
exclude: [],
|
|
724
1153
|
override: []
|
|
725
1154
|
}
|
|
726
1155
|
};
|
|
727
|
-
|
|
728
|
-
return
|
|
1156
|
+
if ("apply" in plugin && typeof plugin.apply === "function") normalized.apply = plugin.apply;
|
|
1157
|
+
return normalized;
|
|
1158
|
+
}
|
|
1159
|
+
async #registerAdapter(adapter) {
|
|
1160
|
+
const source = inputToAdapterSource(this.config);
|
|
1161
|
+
this.#studio.source = source;
|
|
1162
|
+
if (adapter.stream) {
|
|
1163
|
+
this.inputNode = await adapter.stream(source);
|
|
1164
|
+
await this.hooks.emit("kubb:debug", {
|
|
1165
|
+
date: /* @__PURE__ */ new Date(),
|
|
1166
|
+
logs: [`✓ Adapter '${adapter.name}' producing input stream`]
|
|
1167
|
+
});
|
|
1168
|
+
} else {
|
|
1169
|
+
const inputNode = await adapter.parse(source);
|
|
1170
|
+
this.inputNode = (0, _kubb_ast.createStreamInput)(arrayToAsyncIterable(inputNode.schemas), arrayToAsyncIterable(inputNode.operations), inputNode.meta);
|
|
1171
|
+
await this.hooks.emit("kubb:debug", {
|
|
1172
|
+
date: /* @__PURE__ */ new Date(),
|
|
1173
|
+
logs: [
|
|
1174
|
+
`✓ Adapter '${adapter.name}' resolved InputNode (wrapped as stream)`,
|
|
1175
|
+
` • Schemas: ${inputNode.schemas.length}`,
|
|
1176
|
+
` • Operations: ${inputNode.operations.length}`
|
|
1177
|
+
]
|
|
1178
|
+
});
|
|
1179
|
+
}
|
|
1180
|
+
}
|
|
1181
|
+
#registerMiddleware(event, middlewareHooks) {
|
|
1182
|
+
const handler = middlewareHooks[event];
|
|
1183
|
+
if (!handler) return;
|
|
1184
|
+
this.hooks.on(event, handler);
|
|
1185
|
+
this.#middlewareListeners.push([event, handler]);
|
|
729
1186
|
}
|
|
730
1187
|
/**
|
|
731
1188
|
* Registers a hook-style plugin's lifecycle handlers on the shared `AsyncEventEmitter`.
|
|
@@ -742,28 +1199,29 @@ var PluginDriver = class PluginDriver {
|
|
|
742
1199
|
*
|
|
743
1200
|
* @internal
|
|
744
1201
|
*/
|
|
745
|
-
|
|
746
|
-
const { hooks } =
|
|
1202
|
+
#registerPlugin(plugin) {
|
|
1203
|
+
const { hooks } = plugin;
|
|
1204
|
+
if (!hooks) return;
|
|
747
1205
|
if (hooks["kubb:plugin:setup"]) {
|
|
748
1206
|
const setupHandler = (globalCtx) => {
|
|
749
1207
|
const pluginCtx = {
|
|
750
1208
|
...globalCtx,
|
|
751
|
-
options:
|
|
1209
|
+
options: plugin.options ?? {},
|
|
752
1210
|
addGenerator: (gen) => {
|
|
753
|
-
this.registerGenerator(
|
|
1211
|
+
this.registerGenerator(plugin.name, gen);
|
|
754
1212
|
},
|
|
755
1213
|
setResolver: (resolver) => {
|
|
756
|
-
this.setPluginResolver(
|
|
1214
|
+
this.setPluginResolver(plugin.name, resolver);
|
|
757
1215
|
},
|
|
758
1216
|
setTransformer: (visitor) => {
|
|
759
|
-
|
|
1217
|
+
plugin.transformer = visitor;
|
|
760
1218
|
},
|
|
761
1219
|
setRenderer: (renderer) => {
|
|
762
|
-
|
|
1220
|
+
plugin.renderer = renderer;
|
|
763
1221
|
},
|
|
764
1222
|
setOptions: (opts) => {
|
|
765
|
-
|
|
766
|
-
...
|
|
1223
|
+
plugin.options = {
|
|
1224
|
+
...plugin.options,
|
|
767
1225
|
...opts
|
|
768
1226
|
};
|
|
769
1227
|
},
|
|
@@ -824,7 +1282,11 @@ var PluginDriver = class PluginDriver {
|
|
|
824
1282
|
if (gen.schema) {
|
|
825
1283
|
const schemaHandler = async (node, ctx) => {
|
|
826
1284
|
if (ctx.plugin.name !== pluginName) return;
|
|
827
|
-
await applyHookResult(
|
|
1285
|
+
await applyHookResult({
|
|
1286
|
+
result: await gen.schema(node, ctx),
|
|
1287
|
+
driver: this,
|
|
1288
|
+
rendererFactory: resolveRenderer()
|
|
1289
|
+
});
|
|
828
1290
|
};
|
|
829
1291
|
this.hooks.on("kubb:generate:schema", schemaHandler);
|
|
830
1292
|
this.#trackHookListener("kubb:generate:schema", schemaHandler);
|
|
@@ -832,7 +1294,11 @@ var PluginDriver = class PluginDriver {
|
|
|
832
1294
|
if (gen.operation) {
|
|
833
1295
|
const operationHandler = async (node, ctx) => {
|
|
834
1296
|
if (ctx.plugin.name !== pluginName) return;
|
|
835
|
-
await applyHookResult(
|
|
1297
|
+
await applyHookResult({
|
|
1298
|
+
result: await gen.operation(node, ctx),
|
|
1299
|
+
driver: this,
|
|
1300
|
+
rendererFactory: resolveRenderer()
|
|
1301
|
+
});
|
|
836
1302
|
};
|
|
837
1303
|
this.hooks.on("kubb:generate:operation", operationHandler);
|
|
838
1304
|
this.#trackHookListener("kubb:generate:operation", operationHandler);
|
|
@@ -840,12 +1306,16 @@ var PluginDriver = class PluginDriver {
|
|
|
840
1306
|
if (gen.operations) {
|
|
841
1307
|
const operationsHandler = async (nodes, ctx) => {
|
|
842
1308
|
if (ctx.plugin.name !== pluginName) return;
|
|
843
|
-
await applyHookResult(
|
|
1309
|
+
await applyHookResult({
|
|
1310
|
+
result: await gen.operations(nodes, ctx),
|
|
1311
|
+
driver: this,
|
|
1312
|
+
rendererFactory: resolveRenderer()
|
|
1313
|
+
});
|
|
844
1314
|
};
|
|
845
1315
|
this.hooks.on("kubb:generate:operations", operationsHandler);
|
|
846
1316
|
this.#trackHookListener("kubb:generate:operations", operationsHandler);
|
|
847
1317
|
}
|
|
848
|
-
this.#
|
|
1318
|
+
this.#eventGeneratorPlugins.add(pluginName);
|
|
849
1319
|
}
|
|
850
1320
|
/**
|
|
851
1321
|
* Returns `true` when at least one generator was registered for the given plugin
|
|
@@ -854,8 +1324,8 @@ var PluginDriver = class PluginDriver {
|
|
|
854
1324
|
* Used by the build loop to decide whether to walk the AST and emit generator events
|
|
855
1325
|
* for a plugin that has no static `plugin.generators`.
|
|
856
1326
|
*/
|
|
857
|
-
|
|
858
|
-
return this.#
|
|
1327
|
+
hasEventGenerators(pluginName) {
|
|
1328
|
+
return this.#eventGeneratorPlugins.has(pluginName);
|
|
859
1329
|
}
|
|
860
1330
|
/**
|
|
861
1331
|
* Unregisters all plugin lifecycle listeners from the shared event emitter.
|
|
@@ -866,7 +1336,20 @@ var PluginDriver = class PluginDriver {
|
|
|
866
1336
|
dispose() {
|
|
867
1337
|
for (const [event, handlers] of this.#hookListeners) for (const handler of handlers) this.hooks.off(event, handler);
|
|
868
1338
|
this.#hookListeners.clear();
|
|
869
|
-
this.#
|
|
1339
|
+
this.#eventGeneratorPlugins.clear();
|
|
1340
|
+
this.#resolvers.clear();
|
|
1341
|
+
this.#defaultResolvers.clear();
|
|
1342
|
+
this.fileManager.dispose();
|
|
1343
|
+
this.inputNode = void 0;
|
|
1344
|
+
this.#studio = {
|
|
1345
|
+
source: void 0,
|
|
1346
|
+
isOpen: false,
|
|
1347
|
+
inputNode: void 0
|
|
1348
|
+
};
|
|
1349
|
+
for (const [event, handler] of this.#middlewareListeners) this.hooks.off(event, handler);
|
|
1350
|
+
}
|
|
1351
|
+
[Symbol.dispose]() {
|
|
1352
|
+
this.dispose();
|
|
870
1353
|
}
|
|
871
1354
|
#trackHookListener(event, handler) {
|
|
872
1355
|
let handlers = this.#hookListeners.get(event);
|
|
@@ -876,16 +1359,10 @@ var PluginDriver = class PluginDriver {
|
|
|
876
1359
|
}
|
|
877
1360
|
handlers.add(handler);
|
|
878
1361
|
}
|
|
879
|
-
#
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
name: "default",
|
|
884
|
-
pluginName
|
|
885
|
-
}));
|
|
886
|
-
this.#defaultResolvers.set(pluginName, resolver);
|
|
887
|
-
return resolver;
|
|
888
|
-
}
|
|
1362
|
+
#getDefaultResolver = memoize(this.#defaultResolvers, (pluginName) => defineResolver(() => ({
|
|
1363
|
+
name: "default",
|
|
1364
|
+
pluginName
|
|
1365
|
+
})));
|
|
889
1366
|
/**
|
|
890
1367
|
* Merges `partial` with the plugin's default resolver and stores the result.
|
|
891
1368
|
* Also mirrors it onto `plugin.resolver` so callers using `getPlugin(name).resolver`
|
|
@@ -893,7 +1370,7 @@ var PluginDriver = class PluginDriver {
|
|
|
893
1370
|
*/
|
|
894
1371
|
setPluginResolver(pluginName, partial) {
|
|
895
1372
|
const merged = {
|
|
896
|
-
...this.#
|
|
1373
|
+
...this.#getDefaultResolver(pluginName),
|
|
897
1374
|
...partial
|
|
898
1375
|
};
|
|
899
1376
|
this.#resolvers.set(pluginName, merged);
|
|
@@ -901,7 +1378,7 @@ var PluginDriver = class PluginDriver {
|
|
|
901
1378
|
if (plugin) plugin.resolver = merged;
|
|
902
1379
|
}
|
|
903
1380
|
getResolver(pluginName) {
|
|
904
|
-
return this.#resolvers.get(pluginName) ?? this.plugins.get(pluginName)?.resolver ?? this.#
|
|
1381
|
+
return this.#resolvers.get(pluginName) ?? this.plugins.get(pluginName)?.resolver ?? this.#getDefaultResolver(pluginName);
|
|
905
1382
|
}
|
|
906
1383
|
getContext(plugin) {
|
|
907
1384
|
const driver = this;
|
|
@@ -911,7 +1388,7 @@ var PluginDriver = class PluginDriver {
|
|
|
911
1388
|
return (0, node_path.resolve)(driver.config.root, driver.config.output.path);
|
|
912
1389
|
},
|
|
913
1390
|
getMode(output) {
|
|
914
|
-
return
|
|
1391
|
+
return KubbDriver.getMode((0, node_path.resolve)(driver.config.root, driver.config.output.path, output.path));
|
|
915
1392
|
},
|
|
916
1393
|
hooks: driver.hooks,
|
|
917
1394
|
plugin,
|
|
@@ -925,8 +1402,11 @@ var PluginDriver = class PluginDriver {
|
|
|
925
1402
|
upsertFile: async (...files) => {
|
|
926
1403
|
driver.fileManager.upsert(...files);
|
|
927
1404
|
},
|
|
928
|
-
get
|
|
929
|
-
return driver.inputNode
|
|
1405
|
+
get meta() {
|
|
1406
|
+
return driver.inputNode?.meta ?? {
|
|
1407
|
+
circularNames: [],
|
|
1408
|
+
enumNames: []
|
|
1409
|
+
};
|
|
930
1410
|
},
|
|
931
1411
|
get adapter() {
|
|
932
1412
|
return driver.adapter;
|
|
@@ -946,13 +1426,14 @@ var PluginDriver = class PluginDriver {
|
|
|
946
1426
|
info(message) {
|
|
947
1427
|
driver.hooks.emit("kubb:info", { message });
|
|
948
1428
|
},
|
|
949
|
-
openInStudio(options) {
|
|
950
|
-
if (!driver.config.devtools || driver.#
|
|
1429
|
+
async openInStudio(options) {
|
|
1430
|
+
if (!driver.config.devtools || driver.#studio.isOpen) return;
|
|
951
1431
|
if (typeof driver.config.devtools !== "object") throw new Error("Devtools must be an object");
|
|
952
|
-
if (!driver.
|
|
953
|
-
driver.#
|
|
954
|
-
const studioUrl = driver.config.devtools?.studioUrl ?? "https://
|
|
955
|
-
|
|
1432
|
+
if (!driver.adapter || !driver.#studio.source) throw new Error("adapter is not defined, make sure you have set the parser in kubb.config.ts");
|
|
1433
|
+
driver.#studio.isOpen = true;
|
|
1434
|
+
const studioUrl = driver.config.devtools?.studioUrl ?? "https://kubb.studio";
|
|
1435
|
+
driver.#studio.inputNode ??= Promise.resolve(driver.adapter.parse(driver.#studio.source));
|
|
1436
|
+
return openInStudio(await driver.#studio.inputNode, studioUrl, options);
|
|
956
1437
|
}
|
|
957
1438
|
};
|
|
958
1439
|
}
|
|
@@ -965,6 +1446,56 @@ var PluginDriver = class PluginDriver {
|
|
|
965
1446
|
return plugin;
|
|
966
1447
|
}
|
|
967
1448
|
};
|
|
1449
|
+
/**
|
|
1450
|
+
* Handles the return value of a plugin AST hook or generator method.
|
|
1451
|
+
*
|
|
1452
|
+
* - Renderer output → rendered via the provided `rendererFactory` (e.g. JSX), files stored in `driver.fileManager`
|
|
1453
|
+
* - `Array<FileNode>` → added directly into `driver.fileManager`
|
|
1454
|
+
* - `void` / `null` / `undefined` → no-op (plugin handled it via `this.upsertFile`)
|
|
1455
|
+
*
|
|
1456
|
+
* Pass a `rendererFactory` (e.g. `jsxRenderer` from `@kubb/renderer-jsx`) when the result
|
|
1457
|
+
* may be a renderer element. Generators that only return `Array<FileNode>` do not need one.
|
|
1458
|
+
*/
|
|
1459
|
+
function applyHookResult({ result, driver, rendererFactory }) {
|
|
1460
|
+
if (!result) return;
|
|
1461
|
+
if (Array.isArray(result)) {
|
|
1462
|
+
driver.fileManager.upsert(...result);
|
|
1463
|
+
return;
|
|
1464
|
+
}
|
|
1465
|
+
if (!rendererFactory) return;
|
|
1466
|
+
const renderer = rendererFactory();
|
|
1467
|
+
if (renderer.stream) {
|
|
1468
|
+
for (const file of renderer.stream(result)) driver.fileManager.upsert(file);
|
|
1469
|
+
renderer.unmount();
|
|
1470
|
+
return;
|
|
1471
|
+
}
|
|
1472
|
+
return applyAsyncRender({
|
|
1473
|
+
renderer,
|
|
1474
|
+
result,
|
|
1475
|
+
driver
|
|
1476
|
+
});
|
|
1477
|
+
}
|
|
1478
|
+
async function applyAsyncRender({ renderer, result, driver }) {
|
|
1479
|
+
await renderer.render(result);
|
|
1480
|
+
driver.fileManager.upsert(...renderer.files);
|
|
1481
|
+
renderer.unmount();
|
|
1482
|
+
}
|
|
1483
|
+
function inputToAdapterSource(config) {
|
|
1484
|
+
const input = config.input;
|
|
1485
|
+
if (!input) throw new Error("[kubb] input is required when using an adapter. Provide input.path or input.data in your config.");
|
|
1486
|
+
if ("data" in input) return {
|
|
1487
|
+
type: "data",
|
|
1488
|
+
data: input.data
|
|
1489
|
+
};
|
|
1490
|
+
if (new URLPath(input.path).isURL) return {
|
|
1491
|
+
type: "path",
|
|
1492
|
+
path: input.path
|
|
1493
|
+
};
|
|
1494
|
+
return {
|
|
1495
|
+
type: "path",
|
|
1496
|
+
path: (0, node_path.resolve)(config.root, input.path)
|
|
1497
|
+
};
|
|
1498
|
+
}
|
|
968
1499
|
//#endregion
|
|
969
1500
|
Object.defineProperty(exports, "DEFAULT_BANNER", {
|
|
970
1501
|
enumerable: true,
|
|
@@ -990,10 +1521,16 @@ Object.defineProperty(exports, "FileManager", {
|
|
|
990
1521
|
return FileManager;
|
|
991
1522
|
}
|
|
992
1523
|
});
|
|
993
|
-
Object.defineProperty(exports, "
|
|
1524
|
+
Object.defineProperty(exports, "KubbDriver", {
|
|
994
1525
|
enumerable: true,
|
|
995
1526
|
get: function() {
|
|
996
|
-
return
|
|
1527
|
+
return KubbDriver;
|
|
1528
|
+
}
|
|
1529
|
+
});
|
|
1530
|
+
Object.defineProperty(exports, "URLPath", {
|
|
1531
|
+
enumerable: true,
|
|
1532
|
+
get: function() {
|
|
1533
|
+
return URLPath;
|
|
997
1534
|
}
|
|
998
1535
|
});
|
|
999
1536
|
Object.defineProperty(exports, "__name", {
|
|
@@ -1014,10 +1551,10 @@ Object.defineProperty(exports, "applyHookResult", {
|
|
|
1014
1551
|
return applyHookResult;
|
|
1015
1552
|
}
|
|
1016
1553
|
});
|
|
1017
|
-
Object.defineProperty(exports, "
|
|
1554
|
+
Object.defineProperty(exports, "definePlugin", {
|
|
1018
1555
|
enumerable: true,
|
|
1019
1556
|
get: function() {
|
|
1020
|
-
return
|
|
1557
|
+
return definePlugin;
|
|
1021
1558
|
}
|
|
1022
1559
|
});
|
|
1023
1560
|
Object.defineProperty(exports, "defineResolver", {
|
|
@@ -1026,11 +1563,29 @@ Object.defineProperty(exports, "defineResolver", {
|
|
|
1026
1563
|
return defineResolver;
|
|
1027
1564
|
}
|
|
1028
1565
|
});
|
|
1566
|
+
Object.defineProperty(exports, "forBatches", {
|
|
1567
|
+
enumerable: true,
|
|
1568
|
+
get: function() {
|
|
1569
|
+
return forBatches;
|
|
1570
|
+
}
|
|
1571
|
+
});
|
|
1572
|
+
Object.defineProperty(exports, "isPromise", {
|
|
1573
|
+
enumerable: true,
|
|
1574
|
+
get: function() {
|
|
1575
|
+
return isPromise;
|
|
1576
|
+
}
|
|
1577
|
+
});
|
|
1029
1578
|
Object.defineProperty(exports, "logLevel", {
|
|
1030
1579
|
enumerable: true,
|
|
1031
1580
|
get: function() {
|
|
1032
1581
|
return logLevel;
|
|
1033
1582
|
}
|
|
1034
1583
|
});
|
|
1584
|
+
Object.defineProperty(exports, "withDrain", {
|
|
1585
|
+
enumerable: true,
|
|
1586
|
+
get: function() {
|
|
1587
|
+
return withDrain;
|
|
1588
|
+
}
|
|
1589
|
+
});
|
|
1035
1590
|
|
|
1036
|
-
//# sourceMappingURL=
|
|
1591
|
+
//# sourceMappingURL=KubbDriver-BXSnJ3qM.cjs.map
|