@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
|
@@ -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,11 +67,386 @@ 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.
|
|
73
448
|
*/
|
|
74
|
-
const DEFAULT_STUDIO_URL = "https://
|
|
449
|
+
const DEFAULT_STUDIO_URL = "https://kubb.studio";
|
|
75
450
|
/**
|
|
76
451
|
* Default banner style written at the top of every generated file.
|
|
77
452
|
*/
|
|
@@ -94,6 +469,42 @@ const logLevel = {
|
|
|
94
469
|
debug: 5
|
|
95
470
|
};
|
|
96
471
|
//#endregion
|
|
472
|
+
//#region src/definePlugin.ts
|
|
473
|
+
/**
|
|
474
|
+
* Wraps a factory function and returns a typed `Plugin` with lifecycle handlers grouped under `hooks`.
|
|
475
|
+
*
|
|
476
|
+
* Handlers live in a single `hooks` object (inspired by Astro integrations).
|
|
477
|
+
* All lifecycle events from `KubbHooks` are available for subscription.
|
|
478
|
+
*
|
|
479
|
+
* @note For real plugins, use a `PluginFactoryOptions` type parameter to get type-safe context in `kubb:plugin:setup`.
|
|
480
|
+
* Plugin names should follow the convention `plugin-<feature>` (e.g., `plugin-react-query`, `plugin-zod`).
|
|
481
|
+
*
|
|
482
|
+
* @example
|
|
483
|
+
* ```ts
|
|
484
|
+
* import { definePlugin } from '@kubb/core'
|
|
485
|
+
*
|
|
486
|
+
* export const pluginTs = definePlugin((options: { prefix?: string } = {}) => ({
|
|
487
|
+
* name: 'plugin-ts',
|
|
488
|
+
* hooks: {
|
|
489
|
+
* 'kubb:plugin:setup'(ctx) {
|
|
490
|
+
* ctx.setResolver(resolverTs)
|
|
491
|
+
* },
|
|
492
|
+
* },
|
|
493
|
+
* }))
|
|
494
|
+
* ```
|
|
495
|
+
*/
|
|
496
|
+
function definePlugin(factory) {
|
|
497
|
+
return (options) => factory(options ?? {});
|
|
498
|
+
}
|
|
499
|
+
/**
|
|
500
|
+
* Returns `'single'` when `fileOrFolder` has a file extension, `'split'` otherwise.
|
|
501
|
+
* Used to determine whether an output path targets a single file or a directory.
|
|
502
|
+
*/
|
|
503
|
+
function getMode(fileOrFolder) {
|
|
504
|
+
if (!fileOrFolder) return "split";
|
|
505
|
+
return extname(fileOrFolder) ? "single" : "split";
|
|
506
|
+
}
|
|
507
|
+
//#endregion
|
|
97
508
|
//#region src/defineResolver.ts
|
|
98
509
|
const stringPatternCache = /* @__PURE__ */ new Map();
|
|
99
510
|
function testPattern(value, pattern) {
|
|
@@ -111,14 +522,12 @@ function testPattern(value, pattern) {
|
|
|
111
522
|
* Checks if an operation matches a pattern for a given filter type (`tag`, `operationId`, `path`, `method`).
|
|
112
523
|
*/
|
|
113
524
|
function matchesOperationPattern(node, type, pattern) {
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
default: return false;
|
|
121
|
-
}
|
|
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;
|
|
122
531
|
}
|
|
123
532
|
/**
|
|
124
533
|
* Checks if a schema matches a pattern for a given filter type (`schemaName`).
|
|
@@ -126,10 +535,8 @@ function matchesOperationPattern(node, type, pattern) {
|
|
|
126
535
|
* Returns `null` when the filter type doesn't apply to schemas.
|
|
127
536
|
*/
|
|
128
537
|
function matchesSchemaPattern(node, type, pattern) {
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
default: return null;
|
|
132
|
-
}
|
|
538
|
+
if (type === "schemaName") return node.name ? testPattern(node.name, pattern) : false;
|
|
539
|
+
return null;
|
|
133
540
|
}
|
|
134
541
|
/**
|
|
135
542
|
* Default name resolver used by `defineResolver`.
|
|
@@ -139,10 +546,9 @@ function matchesSchemaPattern(node, type, pattern) {
|
|
|
139
546
|
* - `camelCase` for everything else.
|
|
140
547
|
*/
|
|
141
548
|
function defaultResolver(name, type) {
|
|
142
|
-
|
|
143
|
-
if (type === "
|
|
144
|
-
|
|
145
|
-
return resolvedName;
|
|
549
|
+
if (type === "file" || type === "function") return camelCase(name, { isFile: type === "file" });
|
|
550
|
+
if (type === "type") return pascalCase(name);
|
|
551
|
+
return camelCase(name);
|
|
146
552
|
}
|
|
147
553
|
/**
|
|
148
554
|
* Default option resolver — applies include/exclude filters and merges matching override options.
|
|
@@ -167,7 +573,8 @@ function defaultResolver(name, type) {
|
|
|
167
573
|
* // → { enumType: 'enum' } when operationId matches
|
|
168
574
|
* ```
|
|
169
575
|
*/
|
|
170
|
-
|
|
576
|
+
const resolveOptionsCache = /* @__PURE__ */ new WeakMap();
|
|
577
|
+
function computeOptions(node, options, exclude, include, override) {
|
|
171
578
|
if (isOperationNode(node)) {
|
|
172
579
|
if (exclude.some(({ type, pattern }) => matchesOperationPattern(node, type, pattern))) return null;
|
|
173
580
|
if (include && !include.some(({ type, pattern }) => matchesOperationPattern(node, type, pattern))) return null;
|
|
@@ -191,6 +598,19 @@ function defaultResolveOptions(node, { options, exclude = [], include, override
|
|
|
191
598
|
}
|
|
192
599
|
return options;
|
|
193
600
|
}
|
|
601
|
+
function defaultResolveOptions(node, { options, exclude = [], include, override = [] }) {
|
|
602
|
+
const optionsKey = options;
|
|
603
|
+
let byOptions = resolveOptionsCache.get(optionsKey);
|
|
604
|
+
if (!byOptions) {
|
|
605
|
+
byOptions = /* @__PURE__ */ new WeakMap();
|
|
606
|
+
resolveOptionsCache.set(optionsKey, byOptions);
|
|
607
|
+
}
|
|
608
|
+
const cached = byOptions.get(node);
|
|
609
|
+
if (cached !== void 0) return cached.value;
|
|
610
|
+
const result = computeOptions(node, options, exclude, include, override);
|
|
611
|
+
byOptions.set(node, { value: result });
|
|
612
|
+
return result;
|
|
613
|
+
}
|
|
194
614
|
/**
|
|
195
615
|
* Default path resolver used by `defineResolver`.
|
|
196
616
|
*
|
|
@@ -236,17 +656,19 @@ function defaultResolveOptions(node, { options, exclude = [], include, override
|
|
|
236
656
|
* ```
|
|
237
657
|
*/
|
|
238
658
|
function defaultResolvePath({ baseName, pathMode, tag, path: groupPath }, { root, output, group }) {
|
|
239
|
-
if ((pathMode ??
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
659
|
+
if ((pathMode ?? getMode(path.resolve(root, output.path))) === "single") return path.resolve(root, output.path);
|
|
660
|
+
const result = (() => {
|
|
661
|
+
if (group && (groupPath || tag)) {
|
|
662
|
+
const groupValue = group.type === "path" ? groupPath : tag;
|
|
663
|
+
const defaultName = group.type === "tag" ? ({ group: g }) => `${camelCase(g)}Controller` : ({ group: g }) => {
|
|
664
|
+
const segment = g.split("/").filter((s) => s !== "" && s !== "." && s !== "..")[0];
|
|
665
|
+
return segment ? camelCase(segment) : "";
|
|
666
|
+
};
|
|
667
|
+
const resolveName = group.name ?? defaultName;
|
|
668
|
+
return path.resolve(root, output.path, resolveName({ group: groupValue }), baseName);
|
|
669
|
+
}
|
|
670
|
+
return path.resolve(root, output.path, baseName);
|
|
671
|
+
})();
|
|
250
672
|
const outputDir = path.resolve(root, output.path);
|
|
251
673
|
const outputDirWithSep = outputDir.endsWith(path.sep) ? outputDir : `${outputDir}${path.sep}`;
|
|
252
674
|
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.`);
|
|
@@ -263,28 +685,28 @@ function defaultResolvePath({ baseName, pathMode, tag, path: groupPath }, { root
|
|
|
263
685
|
*
|
|
264
686
|
* @example Resolve a schema file
|
|
265
687
|
* ```ts
|
|
266
|
-
* const file = defaultResolveFile(
|
|
688
|
+
* const file = defaultResolveFile.call(
|
|
689
|
+
* resolver,
|
|
267
690
|
* { name: 'pet', extname: '.ts' },
|
|
268
691
|
* { root: '/src', output: { path: 'types' } },
|
|
269
|
-
* resolver,
|
|
270
692
|
* )
|
|
271
693
|
* // → { baseName: 'pet.ts', path: '/src/types/pet.ts', sources: [], ... }
|
|
272
694
|
* ```
|
|
273
695
|
*
|
|
274
696
|
* @example Resolve an operation file with tag grouping
|
|
275
697
|
* ```ts
|
|
276
|
-
* const file = defaultResolveFile(
|
|
698
|
+
* const file = defaultResolveFile.call(
|
|
699
|
+
* resolver,
|
|
277
700
|
* { name: 'listPets', extname: '.ts', tag: 'pets' },
|
|
278
701
|
* { root: '/src', output: { path: 'types' }, group: { type: 'tag' } },
|
|
279
|
-
* resolver,
|
|
280
702
|
* )
|
|
281
703
|
* // → { baseName: 'listPets.ts', path: '/src/types/petsController/listPets.ts', ... }
|
|
282
704
|
* ```
|
|
283
705
|
*/
|
|
284
|
-
function defaultResolveFile({ name, extname, tag, path: groupPath }, context
|
|
285
|
-
const pathMode =
|
|
286
|
-
const baseName = `${pathMode === "single" ? "" :
|
|
287
|
-
const filePath =
|
|
706
|
+
function defaultResolveFile({ name, extname, tag, path: groupPath }, context) {
|
|
707
|
+
const pathMode = getMode(path.resolve(context.root, context.output.path));
|
|
708
|
+
const baseName = `${pathMode === "single" ? "" : this.default(name, "file")}${extname}`;
|
|
709
|
+
const filePath = this.resolvePath({
|
|
288
710
|
baseName,
|
|
289
711
|
pathMode,
|
|
290
712
|
tag,
|
|
@@ -293,7 +715,7 @@ function defaultResolveFile({ name, extname, tag, path: groupPath }, context, ct
|
|
|
293
715
|
return createFile({
|
|
294
716
|
path: filePath,
|
|
295
717
|
baseName: path.basename(filePath),
|
|
296
|
-
meta: { pluginName:
|
|
718
|
+
meta: { pluginName: this.pluginName },
|
|
297
719
|
sources: [],
|
|
298
720
|
imports: [],
|
|
299
721
|
exports: []
|
|
@@ -304,12 +726,16 @@ function defaultResolveFile({ name, extname, tag, path: groupPath }, context, ct
|
|
|
304
726
|
*/
|
|
305
727
|
function buildDefaultBanner({ title, description, version, config }) {
|
|
306
728
|
try {
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
729
|
+
const source = (() => {
|
|
730
|
+
if (Array.isArray(config.input)) {
|
|
731
|
+
const first = config.input[0];
|
|
732
|
+
if (first && "path" in first) return path.basename(first.path);
|
|
733
|
+
return "";
|
|
734
|
+
}
|
|
735
|
+
if (config.input && "path" in config.input) return path.basename(config.input.path);
|
|
736
|
+
if (config.input && "data" in config.input) return "text content";
|
|
737
|
+
return "";
|
|
738
|
+
})();
|
|
313
739
|
let banner = "/**\n* Generated by Kubb (https://kubb.dev/).\n* Do not edit manually.\n";
|
|
314
740
|
if (config.output.defaultBanner === "simple") {
|
|
315
741
|
banner += "*/\n";
|
|
@@ -333,10 +759,9 @@ function buildDefaultBanner({ title, description, version, config }) {
|
|
|
333
759
|
*
|
|
334
760
|
* A user-supplied `output.banner` overrides the default Kubb "Generated by Kubb" notice.
|
|
335
761
|
* When no `output.banner` is set, the Kubb notice is used (including `title` and `version`
|
|
336
|
-
* from the
|
|
762
|
+
* from the document metadata when `meta` is provided).
|
|
337
763
|
*
|
|
338
|
-
* - When `output.banner` is a function
|
|
339
|
-
* - 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.
|
|
340
765
|
* - When `output.banner` is a string, returns it directly.
|
|
341
766
|
* - When `config.output.defaultBanner` is `false`, returns `undefined`.
|
|
342
767
|
* - Otherwise returns the Kubb "Generated by Kubb" notice.
|
|
@@ -347,15 +772,15 @@ function buildDefaultBanner({ title, description, version, config }) {
|
|
|
347
772
|
* // → '// my banner'
|
|
348
773
|
* ```
|
|
349
774
|
*
|
|
350
|
-
* @example Function banner with
|
|
775
|
+
* @example Function banner with metadata
|
|
351
776
|
* ```ts
|
|
352
|
-
* defaultResolveBanner(
|
|
777
|
+
* defaultResolveBanner(meta, { output: { banner: (m) => `// v${m?.version}` }, config })
|
|
353
778
|
* // → '// v3.0.0'
|
|
354
779
|
* ```
|
|
355
780
|
*
|
|
356
781
|
* @example No user banner — Kubb notice with OAS metadata
|
|
357
782
|
* ```ts
|
|
358
|
-
* defaultResolveBanner(
|
|
783
|
+
* defaultResolveBanner(meta, { config })
|
|
359
784
|
* // → '/** Generated by Kubb ... Title: Pet Store ... *\/'
|
|
360
785
|
* ```
|
|
361
786
|
*
|
|
@@ -365,21 +790,20 @@ function buildDefaultBanner({ title, description, version, config }) {
|
|
|
365
790
|
* // → undefined
|
|
366
791
|
* ```
|
|
367
792
|
*/
|
|
368
|
-
function defaultResolveBanner(
|
|
369
|
-
if (typeof output?.banner === "function") return output.banner(
|
|
793
|
+
function defaultResolveBanner(meta, { output, config }) {
|
|
794
|
+
if (typeof output?.banner === "function") return output.banner(meta);
|
|
370
795
|
if (typeof output?.banner === "string") return output.banner;
|
|
371
796
|
if (config.output.defaultBanner === false) return;
|
|
372
797
|
return buildDefaultBanner({
|
|
373
|
-
title:
|
|
374
|
-
version:
|
|
798
|
+
title: meta?.title,
|
|
799
|
+
version: meta?.version,
|
|
375
800
|
config
|
|
376
801
|
});
|
|
377
802
|
}
|
|
378
803
|
/**
|
|
379
804
|
* Default footer resolver — returns the footer string for a generated file.
|
|
380
805
|
*
|
|
381
|
-
* - When `output.footer` is a function
|
|
382
|
-
* - 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.
|
|
383
807
|
* - When `output.footer` is a string, returns it directly.
|
|
384
808
|
* - Otherwise returns `undefined`.
|
|
385
809
|
*
|
|
@@ -389,14 +813,14 @@ function defaultResolveBanner(node, { output, config }) {
|
|
|
389
813
|
* // → '// end of file'
|
|
390
814
|
* ```
|
|
391
815
|
*
|
|
392
|
-
* @example Function footer with
|
|
816
|
+
* @example Function footer with metadata
|
|
393
817
|
* ```ts
|
|
394
|
-
* defaultResolveFooter(
|
|
818
|
+
* defaultResolveFooter(meta, { output: { footer: (m) => `// ${m?.title}` }, config })
|
|
395
819
|
* // → '// Pet Store'
|
|
396
820
|
* ```
|
|
397
821
|
*/
|
|
398
|
-
function defaultResolveFooter(
|
|
399
|
-
if (typeof output?.footer === "function") return
|
|
822
|
+
function defaultResolveFooter(meta, { output }) {
|
|
823
|
+
if (typeof output?.footer === "function") return output.footer(meta);
|
|
400
824
|
if (typeof output?.footer === "string") return output.footer;
|
|
401
825
|
}
|
|
402
826
|
/**
|
|
@@ -409,25 +833,24 @@ function defaultResolveFooter(node, { output }) {
|
|
|
409
833
|
* - `resolvePath` — output path computation
|
|
410
834
|
* - `resolveFile` — full `FileNode` construction
|
|
411
835
|
*
|
|
412
|
-
*
|
|
413
|
-
* call sibling resolver methods using `ctx` instead of `this`.
|
|
836
|
+
* Methods in the returned object can call sibling resolver methods via `this`.
|
|
414
837
|
*
|
|
415
838
|
* @example Basic resolver with naming helpers
|
|
416
839
|
* ```ts
|
|
417
|
-
* export const resolver = defineResolver<PluginTs>((
|
|
840
|
+
* export const resolver = defineResolver<PluginTs>(() => ({
|
|
418
841
|
* name: 'default',
|
|
419
842
|
* resolveName(node) {
|
|
420
|
-
* return
|
|
843
|
+
* return this.default(node.name, 'function')
|
|
421
844
|
* },
|
|
422
845
|
* resolveTypedName(node) {
|
|
423
|
-
* return
|
|
846
|
+
* return this.default(node.name, 'type')
|
|
424
847
|
* },
|
|
425
848
|
* }))
|
|
426
849
|
* ```
|
|
427
850
|
*
|
|
428
851
|
* @example Override resolvePath for a custom output structure
|
|
429
852
|
* ```ts
|
|
430
|
-
* export const resolver = defineResolver<PluginTs>((
|
|
853
|
+
* export const resolver = defineResolver<PluginTs>(() => ({
|
|
431
854
|
* name: 'custom',
|
|
432
855
|
* resolvePath({ baseName }, { root, output }) {
|
|
433
856
|
* return path.resolve(root, output.path, 'generated', baseName)
|
|
@@ -435,27 +858,27 @@ function defaultResolveFooter(node, { output }) {
|
|
|
435
858
|
* }))
|
|
436
859
|
* ```
|
|
437
860
|
*
|
|
438
|
-
* @example Use
|
|
861
|
+
* @example Use this.default inside a helper
|
|
439
862
|
* ```ts
|
|
440
|
-
* export const resolver = defineResolver<PluginTs>((
|
|
863
|
+
* export const resolver = defineResolver<PluginTs>(() => ({
|
|
441
864
|
* name: 'default',
|
|
442
865
|
* resolveParamName(node, param) {
|
|
443
|
-
* return
|
|
866
|
+
* return this.default(`${node.operationId} ${param.in} ${param.name}`, 'type')
|
|
444
867
|
* },
|
|
445
868
|
* }))
|
|
446
869
|
* ```
|
|
447
870
|
*/
|
|
448
871
|
function defineResolver(build) {
|
|
449
|
-
|
|
450
|
-
|
|
872
|
+
let resolver;
|
|
873
|
+
resolver = {
|
|
451
874
|
default: defaultResolver,
|
|
452
875
|
resolveOptions: defaultResolveOptions,
|
|
453
876
|
resolvePath: defaultResolvePath,
|
|
454
|
-
resolveFile: (params, context) => defaultResolveFile(params, context
|
|
877
|
+
resolveFile: (params, context) => defaultResolveFile.call(resolver, params, context),
|
|
455
878
|
resolveBanner: defaultResolveBanner,
|
|
456
879
|
resolveFooter: defaultResolveFooter,
|
|
457
|
-
...build(
|
|
458
|
-
}
|
|
880
|
+
...build()
|
|
881
|
+
};
|
|
459
882
|
return resolver;
|
|
460
883
|
}
|
|
461
884
|
//#endregion
|
|
@@ -583,6 +1006,16 @@ var FileManager = class {
|
|
|
583
1006
|
this.#filesCache = null;
|
|
584
1007
|
}
|
|
585
1008
|
/**
|
|
1009
|
+
* Releases all stored files. Called by the core after `kubb:build:end` to
|
|
1010
|
+
* free the per-plugin FileNode caches for the rest of the process lifetime.
|
|
1011
|
+
*/
|
|
1012
|
+
dispose() {
|
|
1013
|
+
this.clear();
|
|
1014
|
+
}
|
|
1015
|
+
[Symbol.dispose]() {
|
|
1016
|
+
this.dispose();
|
|
1017
|
+
}
|
|
1018
|
+
/**
|
|
586
1019
|
* All stored files, sorted by path length (shorter paths first).
|
|
587
1020
|
*/
|
|
588
1021
|
get files() {
|
|
@@ -600,35 +1033,11 @@ var FileManager = class {
|
|
|
600
1033
|
}
|
|
601
1034
|
};
|
|
602
1035
|
//#endregion
|
|
603
|
-
//#region src/
|
|
604
|
-
/**
|
|
605
|
-
* Handles the return value of a plugin AST hook or generator method.
|
|
606
|
-
*
|
|
607
|
-
* - Renderer output → rendered via the provided `rendererFactory` (e.g. JSX), files stored in `driver.fileManager`
|
|
608
|
-
* - `Array<FileNode>` → added directly into `driver.fileManager`
|
|
609
|
-
* - `void` / `null` / `undefined` → no-op (plugin handled it via `this.upsertFile`)
|
|
610
|
-
*
|
|
611
|
-
* Pass a `rendererFactory` (e.g. `jsxRenderer` from `@kubb/renderer-jsx`) when the result
|
|
612
|
-
* may be a renderer element. Generators that only return `Array<FileNode>` do not need one.
|
|
613
|
-
*/
|
|
614
|
-
async function applyHookResult(result, driver, rendererFactory) {
|
|
615
|
-
if (!result) return;
|
|
616
|
-
if (Array.isArray(result)) {
|
|
617
|
-
driver.fileManager.upsert(...result);
|
|
618
|
-
return;
|
|
619
|
-
}
|
|
620
|
-
if (!rendererFactory) return;
|
|
621
|
-
const renderer = rendererFactory();
|
|
622
|
-
await renderer.render(result);
|
|
623
|
-
driver.fileManager.upsert(...renderer.files);
|
|
624
|
-
renderer.unmount();
|
|
625
|
-
}
|
|
626
|
-
//#endregion
|
|
627
|
-
//#region src/PluginDriver.ts
|
|
1036
|
+
//#region src/KubbDriver.ts
|
|
628
1037
|
function enforceOrder(enforce) {
|
|
629
1038
|
return enforce === "pre" ? -1 : enforce === "post" ? 1 : 0;
|
|
630
1039
|
}
|
|
631
|
-
var
|
|
1040
|
+
var KubbDriver = class KubbDriver {
|
|
632
1041
|
config;
|
|
633
1042
|
options;
|
|
634
1043
|
/**
|
|
@@ -636,21 +1045,34 @@ var PluginDriver = class PluginDriver {
|
|
|
636
1045
|
*
|
|
637
1046
|
* @example
|
|
638
1047
|
* ```ts
|
|
639
|
-
*
|
|
640
|
-
*
|
|
1048
|
+
* KubbDriver.getMode('src/gen/types.ts') // 'single'
|
|
1049
|
+
* KubbDriver.getMode('src/gen/types') // 'split'
|
|
641
1050
|
* ```
|
|
642
1051
|
*/
|
|
643
1052
|
static getMode(fileOrFolder) {
|
|
644
|
-
|
|
645
|
-
return extname(fileOrFolder) ? "single" : "split";
|
|
1053
|
+
return getMode(fileOrFolder);
|
|
646
1054
|
}
|
|
647
1055
|
/**
|
|
648
|
-
* The
|
|
649
|
-
*
|
|
1056
|
+
* The streaming `InputStreamNode` produced by the adapter.
|
|
1057
|
+
* Always set after adapter setup — parse-only adapters are wrapped automatically.
|
|
650
1058
|
*/
|
|
651
1059
|
inputNode = void 0;
|
|
652
1060
|
adapter = void 0;
|
|
653
|
-
|
|
1061
|
+
/**
|
|
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.
|
|
1069
|
+
*/
|
|
1070
|
+
#studio = {
|
|
1071
|
+
source: void 0,
|
|
1072
|
+
isOpen: false,
|
|
1073
|
+
inputNode: void 0
|
|
1074
|
+
};
|
|
1075
|
+
#middlewareListeners = [];
|
|
654
1076
|
/**
|
|
655
1077
|
* Central file store for all generated files.
|
|
656
1078
|
* Plugins should use `this.addFile()` / `this.upsertFile()` (via their context) to
|
|
@@ -662,23 +1084,29 @@ var PluginDriver = class PluginDriver {
|
|
|
662
1084
|
* Tracks which plugins have generators registered via `addGenerator()` (event-based path).
|
|
663
1085
|
* Used by the build loop to decide whether to emit generator events for a given plugin.
|
|
664
1086
|
*/
|
|
665
|
-
#
|
|
1087
|
+
#eventGeneratorPlugins = /* @__PURE__ */ new Set();
|
|
666
1088
|
#resolvers = /* @__PURE__ */ new Map();
|
|
667
1089
|
#defaultResolvers = /* @__PURE__ */ new Map();
|
|
668
1090
|
#hookListeners = /* @__PURE__ */ new Map();
|
|
669
1091
|
constructor(config, options) {
|
|
670
1092
|
this.config = config;
|
|
671
1093
|
this.options = options;
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
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) => {
|
|
676
1099
|
if (b.dependencies?.includes(a.name)) return -1;
|
|
677
1100
|
if (a.dependencies?.includes(b.name)) return 1;
|
|
678
1101
|
return enforceOrder(a.enforce) - enforceOrder(b.enforce);
|
|
679
|
-
}).forEach((plugin) => {
|
|
680
|
-
this.plugins.set(plugin.name, plugin);
|
|
681
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);
|
|
682
1110
|
}
|
|
683
1111
|
get hooks() {
|
|
684
1112
|
return this.options.hooks;
|
|
@@ -687,19 +1115,48 @@ var PluginDriver = class PluginDriver {
|
|
|
687
1115
|
* Creates an `NormalizedPlugin` from a hook-style plugin and registers
|
|
688
1116
|
* its lifecycle handlers on the `AsyncEventEmitter`.
|
|
689
1117
|
*/
|
|
690
|
-
#normalizePlugin(
|
|
691
|
-
const
|
|
692
|
-
name:
|
|
693
|
-
dependencies:
|
|
694
|
-
enforce:
|
|
695
|
-
|
|
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 ?? {
|
|
696
1125
|
output: { path: "." },
|
|
697
1126
|
exclude: [],
|
|
698
1127
|
override: []
|
|
699
1128
|
}
|
|
700
1129
|
};
|
|
701
|
-
|
|
702
|
-
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]);
|
|
703
1160
|
}
|
|
704
1161
|
/**
|
|
705
1162
|
* Registers a hook-style plugin's lifecycle handlers on the shared `AsyncEventEmitter`.
|
|
@@ -716,28 +1173,29 @@ var PluginDriver = class PluginDriver {
|
|
|
716
1173
|
*
|
|
717
1174
|
* @internal
|
|
718
1175
|
*/
|
|
719
|
-
|
|
720
|
-
const { hooks } =
|
|
1176
|
+
#registerPlugin(plugin) {
|
|
1177
|
+
const { hooks } = plugin;
|
|
1178
|
+
if (!hooks) return;
|
|
721
1179
|
if (hooks["kubb:plugin:setup"]) {
|
|
722
1180
|
const setupHandler = (globalCtx) => {
|
|
723
1181
|
const pluginCtx = {
|
|
724
1182
|
...globalCtx,
|
|
725
|
-
options:
|
|
1183
|
+
options: plugin.options ?? {},
|
|
726
1184
|
addGenerator: (gen) => {
|
|
727
|
-
this.registerGenerator(
|
|
1185
|
+
this.registerGenerator(plugin.name, gen);
|
|
728
1186
|
},
|
|
729
1187
|
setResolver: (resolver) => {
|
|
730
|
-
this.setPluginResolver(
|
|
1188
|
+
this.setPluginResolver(plugin.name, resolver);
|
|
731
1189
|
},
|
|
732
1190
|
setTransformer: (visitor) => {
|
|
733
|
-
|
|
1191
|
+
plugin.transformer = visitor;
|
|
734
1192
|
},
|
|
735
1193
|
setRenderer: (renderer) => {
|
|
736
|
-
|
|
1194
|
+
plugin.renderer = renderer;
|
|
737
1195
|
},
|
|
738
1196
|
setOptions: (opts) => {
|
|
739
|
-
|
|
740
|
-
...
|
|
1197
|
+
plugin.options = {
|
|
1198
|
+
...plugin.options,
|
|
741
1199
|
...opts
|
|
742
1200
|
};
|
|
743
1201
|
},
|
|
@@ -798,7 +1256,11 @@ var PluginDriver = class PluginDriver {
|
|
|
798
1256
|
if (gen.schema) {
|
|
799
1257
|
const schemaHandler = async (node, ctx) => {
|
|
800
1258
|
if (ctx.plugin.name !== pluginName) return;
|
|
801
|
-
await applyHookResult(
|
|
1259
|
+
await applyHookResult({
|
|
1260
|
+
result: await gen.schema(node, ctx),
|
|
1261
|
+
driver: this,
|
|
1262
|
+
rendererFactory: resolveRenderer()
|
|
1263
|
+
});
|
|
802
1264
|
};
|
|
803
1265
|
this.hooks.on("kubb:generate:schema", schemaHandler);
|
|
804
1266
|
this.#trackHookListener("kubb:generate:schema", schemaHandler);
|
|
@@ -806,7 +1268,11 @@ var PluginDriver = class PluginDriver {
|
|
|
806
1268
|
if (gen.operation) {
|
|
807
1269
|
const operationHandler = async (node, ctx) => {
|
|
808
1270
|
if (ctx.plugin.name !== pluginName) return;
|
|
809
|
-
await applyHookResult(
|
|
1271
|
+
await applyHookResult({
|
|
1272
|
+
result: await gen.operation(node, ctx),
|
|
1273
|
+
driver: this,
|
|
1274
|
+
rendererFactory: resolveRenderer()
|
|
1275
|
+
});
|
|
810
1276
|
};
|
|
811
1277
|
this.hooks.on("kubb:generate:operation", operationHandler);
|
|
812
1278
|
this.#trackHookListener("kubb:generate:operation", operationHandler);
|
|
@@ -814,12 +1280,16 @@ var PluginDriver = class PluginDriver {
|
|
|
814
1280
|
if (gen.operations) {
|
|
815
1281
|
const operationsHandler = async (nodes, ctx) => {
|
|
816
1282
|
if (ctx.plugin.name !== pluginName) return;
|
|
817
|
-
await applyHookResult(
|
|
1283
|
+
await applyHookResult({
|
|
1284
|
+
result: await gen.operations(nodes, ctx),
|
|
1285
|
+
driver: this,
|
|
1286
|
+
rendererFactory: resolveRenderer()
|
|
1287
|
+
});
|
|
818
1288
|
};
|
|
819
1289
|
this.hooks.on("kubb:generate:operations", operationsHandler);
|
|
820
1290
|
this.#trackHookListener("kubb:generate:operations", operationsHandler);
|
|
821
1291
|
}
|
|
822
|
-
this.#
|
|
1292
|
+
this.#eventGeneratorPlugins.add(pluginName);
|
|
823
1293
|
}
|
|
824
1294
|
/**
|
|
825
1295
|
* Returns `true` when at least one generator was registered for the given plugin
|
|
@@ -828,8 +1298,8 @@ var PluginDriver = class PluginDriver {
|
|
|
828
1298
|
* Used by the build loop to decide whether to walk the AST and emit generator events
|
|
829
1299
|
* for a plugin that has no static `plugin.generators`.
|
|
830
1300
|
*/
|
|
831
|
-
|
|
832
|
-
return this.#
|
|
1301
|
+
hasEventGenerators(pluginName) {
|
|
1302
|
+
return this.#eventGeneratorPlugins.has(pluginName);
|
|
833
1303
|
}
|
|
834
1304
|
/**
|
|
835
1305
|
* Unregisters all plugin lifecycle listeners from the shared event emitter.
|
|
@@ -840,7 +1310,20 @@ var PluginDriver = class PluginDriver {
|
|
|
840
1310
|
dispose() {
|
|
841
1311
|
for (const [event, handlers] of this.#hookListeners) for (const handler of handlers) this.hooks.off(event, handler);
|
|
842
1312
|
this.#hookListeners.clear();
|
|
843
|
-
this.#
|
|
1313
|
+
this.#eventGeneratorPlugins.clear();
|
|
1314
|
+
this.#resolvers.clear();
|
|
1315
|
+
this.#defaultResolvers.clear();
|
|
1316
|
+
this.fileManager.dispose();
|
|
1317
|
+
this.inputNode = void 0;
|
|
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);
|
|
1324
|
+
}
|
|
1325
|
+
[Symbol.dispose]() {
|
|
1326
|
+
this.dispose();
|
|
844
1327
|
}
|
|
845
1328
|
#trackHookListener(event, handler) {
|
|
846
1329
|
let handlers = this.#hookListeners.get(event);
|
|
@@ -850,16 +1333,10 @@ var PluginDriver = class PluginDriver {
|
|
|
850
1333
|
}
|
|
851
1334
|
handlers.add(handler);
|
|
852
1335
|
}
|
|
853
|
-
#
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
name: "default",
|
|
858
|
-
pluginName
|
|
859
|
-
}));
|
|
860
|
-
this.#defaultResolvers.set(pluginName, resolver);
|
|
861
|
-
return resolver;
|
|
862
|
-
}
|
|
1336
|
+
#getDefaultResolver = memoize(this.#defaultResolvers, (pluginName) => defineResolver(() => ({
|
|
1337
|
+
name: "default",
|
|
1338
|
+
pluginName
|
|
1339
|
+
})));
|
|
863
1340
|
/**
|
|
864
1341
|
* Merges `partial` with the plugin's default resolver and stores the result.
|
|
865
1342
|
* Also mirrors it onto `plugin.resolver` so callers using `getPlugin(name).resolver`
|
|
@@ -867,7 +1344,7 @@ var PluginDriver = class PluginDriver {
|
|
|
867
1344
|
*/
|
|
868
1345
|
setPluginResolver(pluginName, partial) {
|
|
869
1346
|
const merged = {
|
|
870
|
-
...this.#
|
|
1347
|
+
...this.#getDefaultResolver(pluginName),
|
|
871
1348
|
...partial
|
|
872
1349
|
};
|
|
873
1350
|
this.#resolvers.set(pluginName, merged);
|
|
@@ -875,7 +1352,7 @@ var PluginDriver = class PluginDriver {
|
|
|
875
1352
|
if (plugin) plugin.resolver = merged;
|
|
876
1353
|
}
|
|
877
1354
|
getResolver(pluginName) {
|
|
878
|
-
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);
|
|
879
1356
|
}
|
|
880
1357
|
getContext(plugin) {
|
|
881
1358
|
const driver = this;
|
|
@@ -885,7 +1362,7 @@ var PluginDriver = class PluginDriver {
|
|
|
885
1362
|
return resolve(driver.config.root, driver.config.output.path);
|
|
886
1363
|
},
|
|
887
1364
|
getMode(output) {
|
|
888
|
-
return
|
|
1365
|
+
return KubbDriver.getMode(resolve(driver.config.root, driver.config.output.path, output.path));
|
|
889
1366
|
},
|
|
890
1367
|
hooks: driver.hooks,
|
|
891
1368
|
plugin,
|
|
@@ -899,8 +1376,11 @@ var PluginDriver = class PluginDriver {
|
|
|
899
1376
|
upsertFile: async (...files) => {
|
|
900
1377
|
driver.fileManager.upsert(...files);
|
|
901
1378
|
},
|
|
902
|
-
get
|
|
903
|
-
return driver.inputNode
|
|
1379
|
+
get meta() {
|
|
1380
|
+
return driver.inputNode?.meta ?? {
|
|
1381
|
+
circularNames: [],
|
|
1382
|
+
enumNames: []
|
|
1383
|
+
};
|
|
904
1384
|
},
|
|
905
1385
|
get adapter() {
|
|
906
1386
|
return driver.adapter;
|
|
@@ -920,13 +1400,14 @@ var PluginDriver = class PluginDriver {
|
|
|
920
1400
|
info(message) {
|
|
921
1401
|
driver.hooks.emit("kubb:info", { message });
|
|
922
1402
|
},
|
|
923
|
-
openInStudio(options) {
|
|
924
|
-
if (!driver.config.devtools || driver.#
|
|
1403
|
+
async openInStudio(options) {
|
|
1404
|
+
if (!driver.config.devtools || driver.#studio.isOpen) return;
|
|
925
1405
|
if (typeof driver.config.devtools !== "object") throw new Error("Devtools must be an object");
|
|
926
|
-
if (!driver.
|
|
927
|
-
driver.#
|
|
928
|
-
const studioUrl = driver.config.devtools?.studioUrl ?? "https://
|
|
929
|
-
|
|
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;
|
|
1408
|
+
const studioUrl = driver.config.devtools?.studioUrl ?? "https://kubb.studio";
|
|
1409
|
+
driver.#studio.inputNode ??= Promise.resolve(driver.adapter.parse(driver.#studio.source));
|
|
1410
|
+
return openInStudio(await driver.#studio.inputNode, studioUrl, options);
|
|
930
1411
|
}
|
|
931
1412
|
};
|
|
932
1413
|
}
|
|
@@ -939,7 +1420,57 @@ var PluginDriver = class PluginDriver {
|
|
|
939
1420
|
return plugin;
|
|
940
1421
|
}
|
|
941
1422
|
};
|
|
1423
|
+
/**
|
|
1424
|
+
* Handles the return value of a plugin AST hook or generator method.
|
|
1425
|
+
*
|
|
1426
|
+
* - Renderer output → rendered via the provided `rendererFactory` (e.g. JSX), files stored in `driver.fileManager`
|
|
1427
|
+
* - `Array<FileNode>` → added directly into `driver.fileManager`
|
|
1428
|
+
* - `void` / `null` / `undefined` → no-op (plugin handled it via `this.upsertFile`)
|
|
1429
|
+
*
|
|
1430
|
+
* Pass a `rendererFactory` (e.g. `jsxRenderer` from `@kubb/renderer-jsx`) when the result
|
|
1431
|
+
* may be a renderer element. Generators that only return `Array<FileNode>` do not need one.
|
|
1432
|
+
*/
|
|
1433
|
+
function applyHookResult({ result, driver, rendererFactory }) {
|
|
1434
|
+
if (!result) return;
|
|
1435
|
+
if (Array.isArray(result)) {
|
|
1436
|
+
driver.fileManager.upsert(...result);
|
|
1437
|
+
return;
|
|
1438
|
+
}
|
|
1439
|
+
if (!rendererFactory) return;
|
|
1440
|
+
const renderer = rendererFactory();
|
|
1441
|
+
if (renderer.stream) {
|
|
1442
|
+
for (const file of renderer.stream(result)) driver.fileManager.upsert(file);
|
|
1443
|
+
renderer.unmount();
|
|
1444
|
+
return;
|
|
1445
|
+
}
|
|
1446
|
+
return applyAsyncRender({
|
|
1447
|
+
renderer,
|
|
1448
|
+
result,
|
|
1449
|
+
driver
|
|
1450
|
+
});
|
|
1451
|
+
}
|
|
1452
|
+
async function applyAsyncRender({ renderer, result, driver }) {
|
|
1453
|
+
await renderer.render(result);
|
|
1454
|
+
driver.fileManager.upsert(...renderer.files);
|
|
1455
|
+
renderer.unmount();
|
|
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
|
+
}
|
|
942
1473
|
//#endregion
|
|
943
|
-
export {
|
|
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 };
|
|
944
1475
|
|
|
945
|
-
//# sourceMappingURL=
|
|
1476
|
+
//# sourceMappingURL=KubbDriver-Cxii_rBp.js.map
|