@kubb/core 5.0.0-beta.84 → 5.0.0-beta.86
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{diagnostics-DuaXn2bT.d.ts → Diagnostics-aWJg-H2d.d.ts} +344 -440
- package/dist/index.cjs +850 -1122
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +38 -94
- package/dist/index.js +812 -1084
- package/dist/index.js.map +1 -1
- package/dist/mocks.cjs +7 -14
- package/dist/mocks.cjs.map +1 -1
- package/dist/mocks.d.ts +1 -1
- package/dist/mocks.js +3 -10
- package/dist/mocks.js.map +1 -1
- package/dist/{memoryStorage-DQ6qXJ8o.cjs → usingCtx-BN2OKJRx.cjs} +182 -520
- package/dist/usingCtx-BN2OKJRx.cjs.map +1 -0
- package/dist/{memoryStorage-BjUIqpYE.js → usingCtx-Cnrm3TcM.js} +179 -481
- package/dist/usingCtx-Cnrm3TcM.js.map +1 -0
- package/package.json +2 -2
- package/dist/memoryStorage-BjUIqpYE.js.map +0 -1
- package/dist/memoryStorage-DQ6qXJ8o.cjs.map +0 -1
package/dist/index.js
CHANGED
|
@@ -1,12 +1,47 @@
|
|
|
1
1
|
import "./rolldown-runtime-C0LytTxp.js";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
2
|
+
import { a as toFilePath, c as BuildError, d as camelCase, i as clean, l as getErrorMessage, n as FileManager, o as toPosixPath, r as Hookable, s as write, t as _usingCtx, u as toError } from "./usingCtx-Cnrm3TcM.js";
|
|
3
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
4
4
|
import { stripVTControlCharacters, styleText } from "node:util";
|
|
5
|
+
import { hash } from "node:crypto";
|
|
5
6
|
import { access, glob, readFile, rm } from "node:fs/promises";
|
|
6
7
|
import path, { join, relative, resolve } from "node:path";
|
|
7
|
-
import { AsyncLocalStorage } from "node:async_hooks";
|
|
8
8
|
import { ast, collectUsedSchemaNames, composeMacros, operationDef, schemaDef, transform } from "@kubb/ast";
|
|
9
9
|
import process$1 from "node:process";
|
|
10
|
+
//#region src/createAdapter.ts
|
|
11
|
+
/**
|
|
12
|
+
* Defines a custom adapter that translates a spec format into Kubb's universal
|
|
13
|
+
* AST, for example GraphQL, gRPC, or AsyncAPI. The built-in `@kubb/adapter-oas`
|
|
14
|
+
* handles OpenAPI/Swagger documents.
|
|
15
|
+
*
|
|
16
|
+
* Adapters must return an `InputNode` from `parse`. That node is what every
|
|
17
|
+
* plugin in the build consumes.
|
|
18
|
+
*
|
|
19
|
+
* @example
|
|
20
|
+
* ```ts
|
|
21
|
+
* import { createAdapter, type AdapterFactoryOptions } from '@kubb/core'
|
|
22
|
+
* import { ast } from '@kubb/ast'
|
|
23
|
+
*
|
|
24
|
+
* type MyAdapter = AdapterFactoryOptions<'my-adapter', { validate?: boolean }>
|
|
25
|
+
*
|
|
26
|
+
* export const myAdapter = createAdapter<MyAdapter>((options) => ({
|
|
27
|
+
* name: 'my-adapter',
|
|
28
|
+
* options,
|
|
29
|
+
* document: null,
|
|
30
|
+
* async parse(_source) {
|
|
31
|
+
* // Convert the source (path or inline data) into an InputNode.
|
|
32
|
+
* return ast.factory.createInput()
|
|
33
|
+
* },
|
|
34
|
+
* getImports: () => [],
|
|
35
|
+
* async validate() {
|
|
36
|
+
* // Throw here when the spec is invalid.
|
|
37
|
+
* },
|
|
38
|
+
* }))
|
|
39
|
+
* ```
|
|
40
|
+
*/
|
|
41
|
+
function createAdapter(build) {
|
|
42
|
+
return (options) => build(options ?? {});
|
|
43
|
+
}
|
|
44
|
+
//#endregion
|
|
10
45
|
//#region ../../internals/utils/src/time.ts
|
|
11
46
|
/**
|
|
12
47
|
* Calculates elapsed time in milliseconds from a high-resolution `process.hrtime` start time.
|
|
@@ -90,21 +125,11 @@ const randomColors = [
|
|
|
90
125
|
*/
|
|
91
126
|
function randomCliColor(text) {
|
|
92
127
|
if (!text) return "";
|
|
93
|
-
|
|
128
|
+
const index = hash("sha256", text, "buffer").readUInt32BE(0) % randomColors.length;
|
|
129
|
+
return styleText(randomColors[index] ?? "white", text);
|
|
94
130
|
}
|
|
95
131
|
//#endregion
|
|
96
132
|
//#region ../../internals/utils/src/promise.ts
|
|
97
|
-
/** Returns `true` when `result` is a thenable `Promise`.
|
|
98
|
-
*
|
|
99
|
-
* @example
|
|
100
|
-
* ```ts
|
|
101
|
-
* isPromise(Promise.resolve(1)) // true
|
|
102
|
-
* isPromise(42) // false
|
|
103
|
-
* ```
|
|
104
|
-
*/
|
|
105
|
-
function isPromise(result) {
|
|
106
|
-
return result !== null && result !== void 0 && typeof result["then"] === "function";
|
|
107
|
-
}
|
|
108
133
|
/**
|
|
109
134
|
* Wraps `factory` with a keyed cache backed by the provided store.
|
|
110
135
|
*
|
|
@@ -143,258 +168,116 @@ function memoize(store, factory) {
|
|
|
143
168
|
return value;
|
|
144
169
|
};
|
|
145
170
|
}
|
|
146
|
-
/**
|
|
147
|
-
* Wraps a plain array in a reusable `AsyncIterable`.
|
|
148
|
-
* Each `[Symbol.asyncIterator]()` call returns a fresh generator so the
|
|
149
|
-
* iterable can be consumed multiple times (e.g. once per plugin pre-scan).
|
|
150
|
-
*
|
|
151
|
-
* @example
|
|
152
|
-
* ```ts
|
|
153
|
-
* const stream = arrayToAsyncIterable([1, 2, 3])
|
|
154
|
-
* for await (const n of stream) console.log(n) // 1, 2, 3
|
|
155
|
-
* ```
|
|
156
|
-
*/
|
|
157
|
-
function arrayToAsyncIterable(arr) {
|
|
158
|
-
return { [Symbol.asyncIterator]() {
|
|
159
|
-
return (async function* () {
|
|
160
|
-
yield* arr;
|
|
161
|
-
})();
|
|
162
|
-
} };
|
|
163
|
-
}
|
|
164
171
|
//#endregion
|
|
165
|
-
//#region
|
|
172
|
+
//#region package.json
|
|
173
|
+
var version = "5.0.0-beta.86";
|
|
174
|
+
//#endregion
|
|
175
|
+
//#region src/constants.ts
|
|
166
176
|
/**
|
|
167
|
-
*
|
|
168
|
-
*
|
|
177
|
+
* Plugin `include` filter types that select operations directly. When one of these is set
|
|
178
|
+
* without a `schemaName` include, the generate phase pre-scans operations to compute the set
|
|
179
|
+
* of schemas they reach, so unreachable schemas can be pruned for that plugin.
|
|
169
180
|
*/
|
|
170
|
-
const
|
|
171
|
-
"
|
|
172
|
-
"
|
|
173
|
-
"
|
|
174
|
-
"
|
|
175
|
-
"
|
|
176
|
-
"case",
|
|
177
|
-
"catch",
|
|
178
|
-
"char",
|
|
179
|
-
"class",
|
|
180
|
-
"const",
|
|
181
|
-
"continue",
|
|
182
|
-
"debugger",
|
|
183
|
-
"default",
|
|
184
|
-
"delete",
|
|
185
|
-
"do",
|
|
186
|
-
"double",
|
|
187
|
-
"else",
|
|
188
|
-
"enum",
|
|
189
|
-
"eval",
|
|
190
|
-
"export",
|
|
191
|
-
"extends",
|
|
192
|
-
"false",
|
|
193
|
-
"final",
|
|
194
|
-
"finally",
|
|
195
|
-
"float",
|
|
196
|
-
"for",
|
|
197
|
-
"function",
|
|
198
|
-
"goto",
|
|
199
|
-
"if",
|
|
200
|
-
"implements",
|
|
201
|
-
"import",
|
|
202
|
-
"in",
|
|
203
|
-
"instanceof",
|
|
204
|
-
"int",
|
|
205
|
-
"interface",
|
|
206
|
-
"let",
|
|
207
|
-
"long",
|
|
208
|
-
"native",
|
|
209
|
-
"new",
|
|
210
|
-
"null",
|
|
211
|
-
"package",
|
|
212
|
-
"private",
|
|
213
|
-
"protected",
|
|
214
|
-
"public",
|
|
215
|
-
"return",
|
|
216
|
-
"short",
|
|
217
|
-
"static",
|
|
218
|
-
"super",
|
|
219
|
-
"switch",
|
|
220
|
-
"synchronized",
|
|
221
|
-
"this",
|
|
222
|
-
"throw",
|
|
223
|
-
"throws",
|
|
224
|
-
"transient",
|
|
225
|
-
"true",
|
|
226
|
-
"try",
|
|
227
|
-
"typeof",
|
|
228
|
-
"var",
|
|
229
|
-
"void",
|
|
230
|
-
"volatile",
|
|
231
|
-
"while",
|
|
232
|
-
"with",
|
|
233
|
-
"yield",
|
|
234
|
-
"Array",
|
|
235
|
-
"Date",
|
|
236
|
-
"hasOwnProperty",
|
|
237
|
-
"Infinity",
|
|
238
|
-
"isFinite",
|
|
239
|
-
"isNaN",
|
|
240
|
-
"isPrototypeOf",
|
|
241
|
-
"length",
|
|
242
|
-
"Math",
|
|
243
|
-
"name",
|
|
244
|
-
"NaN",
|
|
245
|
-
"Number",
|
|
246
|
-
"Object",
|
|
247
|
-
"prototype",
|
|
248
|
-
"String",
|
|
249
|
-
"toString",
|
|
250
|
-
"undefined",
|
|
251
|
-
"valueOf"
|
|
181
|
+
const OPERATION_FILTER_TYPES = /* @__PURE__ */ new Set([
|
|
182
|
+
"tag",
|
|
183
|
+
"operationId",
|
|
184
|
+
"path",
|
|
185
|
+
"method",
|
|
186
|
+
"contentType"
|
|
252
187
|
]);
|
|
253
188
|
/**
|
|
254
|
-
*
|
|
255
|
-
*
|
|
256
|
-
*
|
|
257
|
-
* ```ts
|
|
258
|
-
* isValidVarName('status') // true
|
|
259
|
-
* isValidVarName('class') // false (reserved word)
|
|
260
|
-
* isValidVarName('42foo') // false (starts with digit)
|
|
261
|
-
* ```
|
|
189
|
+
* Stable codes Kubb attaches to a `Diagnostic`. Each maps to a known failure mode
|
|
190
|
+
* and stays stable so it can be referenced in tooling and (later) docs. Reference
|
|
191
|
+
* these instead of inlining the string at a throw site.
|
|
262
192
|
*/
|
|
263
|
-
|
|
264
|
-
if (!name || reservedWords.has(name)) return false;
|
|
265
|
-
return isIdentifier(name);
|
|
266
|
-
}
|
|
267
|
-
/**
|
|
268
|
-
* Returns `true` when `name` is syntactically a valid identifier, ignoring reserved words.
|
|
269
|
-
*
|
|
270
|
-
* Reserved words and globals (`class`, `name`, `Date`, …) are valid as bare object-literal keys
|
|
271
|
-
* even though they are not valid variable names, so use this (not {@link isValidVarName}) when
|
|
272
|
-
* deciding whether an object key needs quoting.
|
|
273
|
-
*
|
|
274
|
-
* @example
|
|
275
|
-
* ```ts
|
|
276
|
-
* isIdentifier('name') // true
|
|
277
|
-
* isIdentifier('x-total')// false
|
|
278
|
-
* ```
|
|
279
|
-
*/
|
|
280
|
-
function isIdentifier(name) {
|
|
281
|
-
return /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(name);
|
|
282
|
-
}
|
|
283
|
-
//#endregion
|
|
284
|
-
//#region ../../internals/utils/src/url.ts
|
|
285
|
-
function transformParam(raw, casing) {
|
|
286
|
-
const param = isValidVarName(raw) ? raw : camelCase(raw);
|
|
287
|
-
return casing === "camelcase" ? camelCase(param) : param;
|
|
288
|
-
}
|
|
289
|
-
function toParamsObject(path, { replacer, casing } = {}) {
|
|
290
|
-
const params = {};
|
|
291
|
-
for (const match of path.matchAll(/\{([^}]+)\}/g)) {
|
|
292
|
-
const param = transformParam(match[1], casing);
|
|
293
|
-
const key = replacer ? replacer(param) : param;
|
|
294
|
-
params[key] = key;
|
|
295
|
-
}
|
|
296
|
-
return Object.keys(params).length > 0 ? params : null;
|
|
297
|
-
}
|
|
298
|
-
/**
|
|
299
|
-
* Helpers for OpenAPI/Swagger paths, plus a thin wrapper over the native `URL`.
|
|
300
|
-
*/
|
|
301
|
-
var Url = class Url {
|
|
193
|
+
const diagnosticCode = {
|
|
302
194
|
/**
|
|
303
|
-
*
|
|
304
|
-
*
|
|
305
|
-
* @example
|
|
306
|
-
* Url.toPath('/pet/{petId}') // '/pet/:petId'
|
|
195
|
+
* Fallback for an unstructured error with no specific code.
|
|
307
196
|
*/
|
|
308
|
-
|
|
309
|
-
return path.replace(/\{([^}]+)\}/g, ":$1");
|
|
310
|
-
}
|
|
197
|
+
unknown: "KUBB_UNKNOWN",
|
|
311
198
|
/**
|
|
312
|
-
*
|
|
313
|
-
* `prefix` is prepended inside the literal, `replacer` transforms each parameter name,
|
|
314
|
-
* and `casing` controls parameter identifier casing.
|
|
315
|
-
*
|
|
316
|
-
* @example
|
|
317
|
-
* Url.toTemplateString('/pet/{petId}') // '`/pet/${petId}`'
|
|
318
|
-
*
|
|
319
|
-
* @example
|
|
320
|
-
* Url.toTemplateString('/pet/{petId}', { prefix: 'https://api' }) // '`https://api/pet/${petId}`'
|
|
199
|
+
* The `input.path` file or URL could not be read.
|
|
321
200
|
*/
|
|
322
|
-
|
|
323
|
-
const result = path.split(/\{([^}]+)\}/).map((part, i) => {
|
|
324
|
-
if (i % 2 === 0) return part;
|
|
325
|
-
const param = transformParam(part, casing);
|
|
326
|
-
return `\${${replacer ? replacer(param) : param}}`;
|
|
327
|
-
}).join("");
|
|
328
|
-
return `\`${prefix ?? ""}${result}\``;
|
|
329
|
-
}
|
|
201
|
+
inputNotFound: "KUBB_INPUT_NOT_FOUND",
|
|
330
202
|
/**
|
|
331
|
-
*
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
*
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
203
|
+
* An adapter was configured without an `input`.
|
|
204
|
+
*/
|
|
205
|
+
inputRequired: "KUBB_INPUT_REQUIRED",
|
|
206
|
+
/**
|
|
207
|
+
* A `$ref` (or equivalent reference) could not be resolved in the source document.
|
|
208
|
+
*/
|
|
209
|
+
refNotFound: "KUBB_REF_NOT_FOUND",
|
|
210
|
+
/**
|
|
211
|
+
* A server variable value is not allowed by its `enum`.
|
|
212
|
+
*/
|
|
213
|
+
invalidServerVariable: "KUBB_INVALID_SERVER_VARIABLE",
|
|
214
|
+
/**
|
|
215
|
+
* A required plugin is missing from the config.
|
|
216
|
+
*/
|
|
217
|
+
pluginNotFound: "KUBB_PLUGIN_NOT_FOUND",
|
|
218
|
+
/**
|
|
219
|
+
* A plugin threw while generating.
|
|
220
|
+
*/
|
|
221
|
+
pluginFailed: "KUBB_PLUGIN_FAILED",
|
|
222
|
+
/**
|
|
223
|
+
* A plugin reported a non-fatal warning through `ctx.warn`.
|
|
224
|
+
*/
|
|
225
|
+
pluginWarning: "KUBB_PLUGIN_WARNING",
|
|
226
|
+
/**
|
|
227
|
+
* A plugin reported an informational message through `ctx.info`.
|
|
228
|
+
*/
|
|
229
|
+
pluginInfo: "KUBB_PLUGIN_INFO",
|
|
230
|
+
/**
|
|
231
|
+
* A schema uses a `format` Kubb does not map to a specific type. Reserved for
|
|
232
|
+
* adapters to emit as a `warning`.
|
|
233
|
+
*/
|
|
234
|
+
unsupportedFormat: "KUBB_UNSUPPORTED_FORMAT",
|
|
235
|
+
/**
|
|
236
|
+
* A referenced schema or operation is marked `deprecated`. Reserved for adapters
|
|
237
|
+
* to emit as an `info`.
|
|
238
|
+
*/
|
|
239
|
+
deprecated: "KUBB_DEPRECATED",
|
|
240
|
+
/**
|
|
241
|
+
* An adapter is required but the config has none. The build cannot read the input
|
|
242
|
+
* without one.
|
|
243
|
+
*/
|
|
244
|
+
adapterRequired: "KUBB_ADAPTER_REQUIRED",
|
|
245
|
+
/**
|
|
246
|
+
* A resolved output path escapes the output directory, which can stem from a path
|
|
247
|
+
* traversal in the spec or a misconfigured `group.name`.
|
|
248
|
+
*/
|
|
249
|
+
pathTraversal: "KUBB_PATH_TRAVERSAL",
|
|
250
|
+
/**
|
|
251
|
+
* A plugin's options are invalid, for example `output.mode: 'file'` paired with a `group` option.
|
|
252
|
+
*/
|
|
253
|
+
invalidPluginOptions: "KUBB_INVALID_PLUGIN_OPTIONS",
|
|
254
|
+
/**
|
|
255
|
+
* A post-generate shell hook (`hooks.done`) exited with a failure.
|
|
256
|
+
*/
|
|
257
|
+
hookFailed: "KUBB_HOOK_FAILED",
|
|
258
|
+
/**
|
|
259
|
+
* The formatter pass over the generated files failed.
|
|
260
|
+
*/
|
|
261
|
+
formatFailed: "KUBB_FORMAT_FAILED",
|
|
262
|
+
/**
|
|
263
|
+
* The linter pass over the generated files failed.
|
|
264
|
+
*/
|
|
265
|
+
lintFailed: "KUBB_LINT_FAILED",
|
|
266
|
+
/**
|
|
267
|
+
* Not a failure. Carries a plugin's elapsed time, summed into the run total.
|
|
268
|
+
*/
|
|
269
|
+
performance: "KUBB_PERFORMANCE",
|
|
270
|
+
/**
|
|
271
|
+
* Not a failure. A newer Kubb version is available on npm.
|
|
272
|
+
*/
|
|
273
|
+
updateAvailable: "KUBB_UPDATE_AVAILABLE"
|
|
356
274
|
};
|
|
357
275
|
//#endregion
|
|
358
|
-
//#region src/
|
|
359
|
-
/**
|
|
360
|
-
* Defines a custom adapter that translates a spec format into Kubb's universal
|
|
361
|
-
* AST, for example GraphQL, gRPC, or AsyncAPI. The built-in `@kubb/adapter-oas`
|
|
362
|
-
* handles OpenAPI/Swagger documents.
|
|
363
|
-
*
|
|
364
|
-
* Adapters must return an `InputNode` from `parse`. That node is what every
|
|
365
|
-
* plugin in the build consumes.
|
|
366
|
-
*
|
|
367
|
-
* @example
|
|
368
|
-
* ```ts
|
|
369
|
-
* import { createAdapter, type AdapterFactoryOptions } from '@kubb/core'
|
|
370
|
-
* import { ast } from '@kubb/ast'
|
|
371
|
-
*
|
|
372
|
-
* type MyAdapter = AdapterFactoryOptions<'my-adapter', { validate?: boolean }>
|
|
373
|
-
*
|
|
374
|
-
* export const myAdapter = createAdapter<MyAdapter>((options) => ({
|
|
375
|
-
* name: 'my-adapter',
|
|
376
|
-
* options,
|
|
377
|
-
* document: null,
|
|
378
|
-
* async parse(_source) {
|
|
379
|
-
* // Convert the source (path or inline data) into an InputNode.
|
|
380
|
-
* return ast.factory.createInput()
|
|
381
|
-
* },
|
|
382
|
-
* getImports: () => [],
|
|
383
|
-
* async validate() {
|
|
384
|
-
* // Throw here when the spec is invalid.
|
|
385
|
-
* },
|
|
386
|
-
* }))
|
|
387
|
-
* ```
|
|
388
|
-
*/
|
|
389
|
-
function createAdapter(build) {
|
|
390
|
-
return (options) => build(options ?? {});
|
|
391
|
-
}
|
|
392
|
-
//#endregion
|
|
393
|
-
//#region src/diagnostics.ts
|
|
276
|
+
//#region src/Diagnostics.ts
|
|
394
277
|
/**
|
|
395
278
|
* Docs major version, derived from the package version so the link tracks the published major.
|
|
396
279
|
*/
|
|
397
|
-
const docsMajor =
|
|
280
|
+
const docsMajor = version.split(".")[0] ?? "5";
|
|
398
281
|
/**
|
|
399
282
|
* Narrows a {@link Diagnostic} to the variant for `code`, or `null` when it does not match.
|
|
400
283
|
*
|
|
@@ -452,9 +335,9 @@ const isUpdate = isKind("update");
|
|
|
452
335
|
* blue info).
|
|
453
336
|
*/
|
|
454
337
|
const severityStyle = {
|
|
455
|
-
error:
|
|
456
|
-
warning:
|
|
457
|
-
info:
|
|
338
|
+
error: "red",
|
|
339
|
+
warning: "yellow",
|
|
340
|
+
info: "blue"
|
|
458
341
|
};
|
|
459
342
|
/**
|
|
460
343
|
* Explanation for every {@link diagnosticCode}. Use {@link Diagnostics.explain} to look one up
|
|
@@ -563,8 +446,8 @@ const diagnosticCatalog = {
|
|
|
563
446
|
*
|
|
564
447
|
* The sink lives in a single `AsyncLocalStorage` in the `@kubb/core` bundle.
|
|
565
448
|
* `Diagnostics.scope` activates it for a run, so anything inside that run (the
|
|
566
|
-
* adapter parse, a
|
|
567
|
-
*
|
|
449
|
+
* adapter parse, a generator) reports through `Diagnostics.report` and lands
|
|
450
|
+
* in the same run.
|
|
568
451
|
*/
|
|
569
452
|
var Diagnostics = class Diagnostics {
|
|
570
453
|
static #reporterStorage = new AsyncLocalStorage();
|
|
@@ -634,12 +517,12 @@ var Diagnostics = class Diagnostics {
|
|
|
634
517
|
return true;
|
|
635
518
|
}
|
|
636
519
|
/**
|
|
637
|
-
* Emits a diagnostic on the run's `kubb:diagnostic`
|
|
638
|
-
* Use it instead of calling `hooks.
|
|
520
|
+
* Emits a diagnostic on the run's `kubb:diagnostic` hook so the loggers render it live.
|
|
521
|
+
* Use it instead of calling `hooks.callHook('kubb:diagnostic', ...)` directly. To collect a
|
|
639
522
|
* diagnostic into the build result from deep in a run, use {@link Diagnostics.report} instead.
|
|
640
523
|
*/
|
|
641
524
|
static async emit(hooks, diagnostic) {
|
|
642
|
-
await hooks.
|
|
525
|
+
await hooks.callHook("kubb:diagnostic", { diagnostic });
|
|
643
526
|
}
|
|
644
527
|
/**
|
|
645
528
|
* Coerces any thrown value into a {@link ProblemDiagnostic}. A {@link Diagnostics.Error}
|
|
@@ -750,7 +633,8 @@ var Diagnostics = class Diagnostics {
|
|
|
750
633
|
* `KUBB_REF_NOT_FOUND` → `https://kubb.dev/docs/5.x/reference/diagnostics/kubb-ref-not-found`.
|
|
751
634
|
*/
|
|
752
635
|
static docsUrl(code) {
|
|
753
|
-
|
|
636
|
+
const slug = code.toLowerCase().replaceAll("_", "-");
|
|
637
|
+
return `https://kubb.dev/docs/${docsMajor}.x/reference/diagnostics/${slug}`;
|
|
754
638
|
}
|
|
755
639
|
/**
|
|
756
640
|
* The catalog entry for a code: its title, cause, and fix. Mirrors the kubb.dev
|
|
@@ -786,7 +670,7 @@ var Diagnostics = class Diagnostics {
|
|
|
786
670
|
*/
|
|
787
671
|
static format(diagnostic) {
|
|
788
672
|
const { code, severity, message } = diagnostic;
|
|
789
|
-
const
|
|
673
|
+
const color = severityStyle[severity];
|
|
790
674
|
const problem = isProblem(diagnostic) ? diagnostic : void 0;
|
|
791
675
|
const tag = styleText(color, styleText("bold", `[${code}]`));
|
|
792
676
|
const headline = problem?.plugin ? `${tag} ${problem.plugin}: ${message}` : `${tag}: ${message}`;
|
|
@@ -856,412 +740,286 @@ function definePlugin(factory) {
|
|
|
856
740
|
return (options) => factory(options ?? {});
|
|
857
741
|
}
|
|
858
742
|
//#endregion
|
|
859
|
-
//#region src/
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
* `banner`/`footer` function. Missing fields default to empty/`false` so the object shape
|
|
863
|
-
* is stable even when a caller (e.g. the barrel plugin) has no document metadata.
|
|
864
|
-
*/
|
|
865
|
-
function buildBannerMeta({ meta, file }) {
|
|
866
|
-
return {
|
|
867
|
-
title: meta?.title,
|
|
868
|
-
description: meta?.description,
|
|
869
|
-
version: meta?.version,
|
|
870
|
-
baseURL: meta?.baseURL,
|
|
871
|
-
circularNames: meta?.circularNames ?? [],
|
|
872
|
-
enumNames: meta?.enumNames ?? [],
|
|
873
|
-
filePath: file?.path ?? "",
|
|
874
|
-
baseName: file?.baseName ?? "",
|
|
875
|
-
isBarrel: file?.isBarrel ?? false,
|
|
876
|
-
isAggregation: file?.isAggregation ?? false
|
|
877
|
-
};
|
|
878
|
-
}
|
|
879
|
-
const stringPatternCache = /* @__PURE__ */ new Map();
|
|
880
|
-
function testPattern(value, pattern) {
|
|
881
|
-
if (typeof pattern === "string") {
|
|
882
|
-
let regex = stringPatternCache.get(pattern);
|
|
883
|
-
if (!regex) {
|
|
884
|
-
regex = new RegExp(pattern);
|
|
885
|
-
stringPatternCache.set(pattern, regex);
|
|
886
|
-
}
|
|
887
|
-
return regex.test(value);
|
|
888
|
-
}
|
|
889
|
-
return value.match(pattern) !== null;
|
|
743
|
+
//#region src/Resolver.ts
|
|
744
|
+
function isNamespace(value) {
|
|
745
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
890
746
|
}
|
|
891
747
|
/**
|
|
892
|
-
*
|
|
893
|
-
*/
|
|
894
|
-
function matchesOperationPattern(node, type, pattern) {
|
|
895
|
-
if (type === "tag") return node.tags.some((tag) => testPattern(tag, pattern));
|
|
896
|
-
if (type === "operationId") return testPattern(node.operationId, pattern);
|
|
897
|
-
if (type === "path") return node.path !== void 0 && testPattern(node.path, pattern);
|
|
898
|
-
if (type === "method") return node.method !== void 0 && testPattern(node.method.toLowerCase(), pattern);
|
|
899
|
-
if (type === "contentType") return node.requestBody?.content?.some((c) => testPattern(c.contentType, pattern)) ?? false;
|
|
900
|
-
return false;
|
|
901
|
-
}
|
|
902
|
-
/**
|
|
903
|
-
* Checks if a schema matches a pattern for a given filter type (`schemaName`).
|
|
904
|
-
*
|
|
905
|
-
* Returns `null` when the filter type doesn't apply to schemas.
|
|
906
|
-
*/
|
|
907
|
-
function matchesSchemaPattern(node, type, pattern) {
|
|
908
|
-
if (type === "schemaName") return node.name ? testPattern(node.name, pattern) : false;
|
|
909
|
-
return null;
|
|
910
|
-
}
|
|
911
|
-
/**
|
|
912
|
-
* Default name resolver used by `defineResolver`.
|
|
913
|
-
*
|
|
914
|
-
* - `camelCase` for `file`, with dotted names split into `/`-joined nested paths.
|
|
915
|
-
* - `PascalCase` for `type`.
|
|
916
|
-
* - `camelCase` for `function` and everything else.
|
|
917
|
-
*/
|
|
918
|
-
function defaultResolver(name, type) {
|
|
919
|
-
if (type === "file") return toFilePath(name);
|
|
920
|
-
if (type === "type") return pascalCase(name);
|
|
921
|
-
return camelCase(name);
|
|
922
|
-
}
|
|
923
|
-
/**
|
|
924
|
-
* Default option resolver. Applies include/exclude filters and merges matching override options.
|
|
748
|
+
* Base constraint for all plugin resolver objects.
|
|
925
749
|
*
|
|
926
|
-
*
|
|
750
|
+
* The built-in machinery lives under `default`. Generators call the top-level `name` and
|
|
751
|
+
* `file`, and a plugin overrides them to set its conventions. Extend with top-level helpers
|
|
752
|
+
* (`typeName`, …) and/or grouped namespaces (`query`, `schema`, …).
|
|
927
753
|
*
|
|
928
|
-
* @example
|
|
754
|
+
* @example Top-level helper
|
|
929
755
|
* ```ts
|
|
930
|
-
*
|
|
931
|
-
*
|
|
932
|
-
*
|
|
933
|
-
* })
|
|
934
|
-
* // → null when node has tag 'internal'
|
|
756
|
+
* type MyResolver = Resolver & {
|
|
757
|
+
* typeName(name: string): string
|
|
758
|
+
* }
|
|
935
759
|
* ```
|
|
936
760
|
*
|
|
937
|
-
* @example
|
|
761
|
+
* @example Grouped namespace
|
|
938
762
|
* ```ts
|
|
939
|
-
*
|
|
940
|
-
*
|
|
941
|
-
*
|
|
942
|
-
*
|
|
943
|
-
*
|
|
763
|
+
* type MyResolver = Resolver & {
|
|
764
|
+
* query: {
|
|
765
|
+
* name(node: OperationNode): string
|
|
766
|
+
* keyName(node: OperationNode): string
|
|
767
|
+
* }
|
|
768
|
+
* }
|
|
944
769
|
* ```
|
|
945
770
|
*/
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
771
|
+
var Resolver = class Resolver {
|
|
772
|
+
static #patternCache = /* @__PURE__ */ new Map();
|
|
773
|
+
static #optionsCache = /* @__PURE__ */ new WeakMap();
|
|
774
|
+
pluginName;
|
|
775
|
+
#options;
|
|
776
|
+
constructor(options) {
|
|
777
|
+
this.pluginName = options.pluginName;
|
|
778
|
+
this.#options = options;
|
|
779
|
+
this.#apply(options);
|
|
780
|
+
}
|
|
781
|
+
/**
|
|
782
|
+
* The built-in resolution machinery. Always reaches the untouched defaults, even when a
|
|
783
|
+
* plugin overrides the top-level `name` or `file`.
|
|
784
|
+
*/
|
|
785
|
+
get default() {
|
|
952
786
|
return {
|
|
953
|
-
|
|
954
|
-
|
|
787
|
+
name: camelCase,
|
|
788
|
+
options: this.#resolveOptions.bind(this),
|
|
789
|
+
path: this.#resolvePath.bind(this),
|
|
790
|
+
file: this.#resolveFile.bind(this),
|
|
791
|
+
banner: this.#resolveBanner.bind(this),
|
|
792
|
+
footer: this.#resolveFooter.bind(this)
|
|
955
793
|
};
|
|
956
794
|
}
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
795
|
+
name(name) {
|
|
796
|
+
return this.default.name(name);
|
|
797
|
+
}
|
|
798
|
+
file(params, context) {
|
|
799
|
+
return this.default.file(params, context);
|
|
800
|
+
}
|
|
801
|
+
/**
|
|
802
|
+
* Merges `override` over `base` and returns a new resolver with helpers re-bound.
|
|
803
|
+
* Each key is replaced wholesale. Used when applying `setResolver` partial overrides.
|
|
804
|
+
*/
|
|
805
|
+
static merge(base, override) {
|
|
806
|
+
const patch = override instanceof Resolver ? override.#options : override;
|
|
807
|
+
return new Resolver({
|
|
808
|
+
...base.#options,
|
|
809
|
+
...patch
|
|
810
|
+
});
|
|
811
|
+
}
|
|
812
|
+
/**
|
|
813
|
+
* Binds each entry of `options` onto the resolver, so `this.name`, `this.default`, and
|
|
814
|
+
* `this.file` resolve there for top-level helpers and namespace methods alike. `default`
|
|
815
|
+
* is skipped so it can't be shadowed.
|
|
816
|
+
*/
|
|
817
|
+
#apply(options) {
|
|
818
|
+
const root = this;
|
|
819
|
+
const bind = (value) => typeof value === "function" ? value.bind(root) : value;
|
|
820
|
+
for (const [key, value] of Object.entries(options)) {
|
|
821
|
+
if (key === "pluginName" || key === "default" || value === void 0) continue;
|
|
822
|
+
root[key] = isNamespace(value) ? Object.fromEntries(Object.entries(value).map(([method, member]) => [method, bind(member)])) : bind(value);
|
|
962
823
|
}
|
|
963
|
-
const overrideOptions = override.find(({ type, pattern }) => matchesSchemaPattern(node, type, pattern) === true)?.options;
|
|
964
|
-
return {
|
|
965
|
-
...options,
|
|
966
|
-
...overrideOptions
|
|
967
|
-
};
|
|
968
824
|
}
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
}
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
*
|
|
988
|
-
*
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
* @example Tag-based grouping
|
|
1002
|
-
* ```ts
|
|
1003
|
-
* defaultResolvePath(
|
|
1004
|
-
* { baseName: 'petTypes.ts', tag: 'pets' },
|
|
1005
|
-
* { root: '/src', output: { path: 'types' }, group: { type: 'tag' } },
|
|
1006
|
-
* )
|
|
1007
|
-
* // → '/src/types/pets/petTypes.ts'
|
|
1008
|
-
* ```
|
|
1009
|
-
*
|
|
1010
|
-
* @example Path-based grouping
|
|
1011
|
-
* ```ts
|
|
1012
|
-
* defaultResolvePath(
|
|
1013
|
-
* { baseName: 'petTypes.ts', path: '/pets/list' },
|
|
1014
|
-
* { root: '/src', output: { path: 'types' }, group: { type: 'path' } },
|
|
1015
|
-
* )
|
|
1016
|
-
* // → '/src/types/pets/petTypes.ts'
|
|
1017
|
-
* ```
|
|
1018
|
-
*
|
|
1019
|
-
* @example Single file (`mode: 'file'`)
|
|
1020
|
-
* ```ts
|
|
1021
|
-
* defaultResolvePath(
|
|
1022
|
-
* { baseName: 'petTypes.ts' },
|
|
1023
|
-
* { root: '/src', output: { path: 'types.ts', mode: 'file' } },
|
|
1024
|
-
* )
|
|
1025
|
-
* // → '/src/types.ts'
|
|
1026
|
-
* ```
|
|
1027
|
-
*/
|
|
1028
|
-
function defaultResolvePath({ baseName, tag, path: groupPath }, { root, output, group }) {
|
|
1029
|
-
if ((output.mode ?? "directory") === "file") return path.resolve(root, output.path);
|
|
1030
|
-
const result = (() => {
|
|
1031
|
-
if (group && (groupPath || tag)) {
|
|
1032
|
-
const groupValue = group.type === "path" ? groupPath : tag;
|
|
1033
|
-
const defaultName = group.type === "tag" ? ({ group: groupName }) => camelCase(groupName) : ({ group: groupName }) => {
|
|
1034
|
-
const segment = groupName.split("/").filter((part) => part !== "" && part !== "." && part !== "..")[0];
|
|
1035
|
-
return segment ? camelCase(segment) : "";
|
|
825
|
+
static #testPattern(value, pattern) {
|
|
826
|
+
if (typeof pattern === "string") {
|
|
827
|
+
let regex = Resolver.#patternCache.get(pattern);
|
|
828
|
+
regex ??= new RegExp(pattern);
|
|
829
|
+
Resolver.#patternCache.set(pattern, regex);
|
|
830
|
+
return regex.test(value);
|
|
831
|
+
}
|
|
832
|
+
return value.match(pattern) !== null;
|
|
833
|
+
}
|
|
834
|
+
static #matchesOperation(node, { type, pattern }) {
|
|
835
|
+
if (type === "tag") return node.tags.some((tag) => Resolver.#testPattern(tag, pattern));
|
|
836
|
+
if (type === "operationId") return Resolver.#testPattern(node.operationId, pattern);
|
|
837
|
+
if (type === "path") return node.path !== void 0 && Resolver.#testPattern(node.path, pattern);
|
|
838
|
+
if (type === "method") return node.method !== void 0 && Resolver.#testPattern(node.method.toLowerCase(), pattern);
|
|
839
|
+
if (type === "contentType") return node.requestBody?.content?.some((c) => Resolver.#testPattern(c.contentType, pattern)) ?? false;
|
|
840
|
+
return false;
|
|
841
|
+
}
|
|
842
|
+
/**
|
|
843
|
+
* Returns `null` when the filter type doesn't apply to schemas, so include rules built
|
|
844
|
+
* from operation filters (e.g. `tag`) don't exclude every schema.
|
|
845
|
+
*/
|
|
846
|
+
static #matchesSchema(node, { type, pattern }) {
|
|
847
|
+
if (type === "schemaName") return node.name ? Resolver.#testPattern(node.name, pattern) : false;
|
|
848
|
+
return null;
|
|
849
|
+
}
|
|
850
|
+
static #computeOptions(node, { options, exclude = [], include, override = [] }) {
|
|
851
|
+
if (operationDef.is(node)) {
|
|
852
|
+
if (exclude.some((filter) => Resolver.#matchesOperation(node, filter))) return null;
|
|
853
|
+
if (include && !include.some((filter) => Resolver.#matchesOperation(node, filter))) return null;
|
|
854
|
+
return {
|
|
855
|
+
...options,
|
|
856
|
+
...override.find((filter) => Resolver.#matchesOperation(node, filter))?.options
|
|
1036
857
|
};
|
|
1037
|
-
const groupName = (group.name ?? defaultName)({ group: groupValue });
|
|
1038
|
-
return path.resolve(root, output.path, groupName, baseName);
|
|
1039
858
|
}
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
code: Diagnostics.code.pathTraversal,
|
|
1046
|
-
severity: "error",
|
|
1047
|
-
message: `Resolved path "${result}" is outside the output directory "${outputDir}".`,
|
|
1048
|
-
help: "This can stem from a path traversal in the OpenAPI specification or a misconfigured `group.name` function. Keep generated paths within the output directory.",
|
|
1049
|
-
location: { kind: "config" }
|
|
1050
|
-
});
|
|
1051
|
-
return result;
|
|
1052
|
-
}
|
|
1053
|
-
/**
|
|
1054
|
-
* Default file resolver used by `defineResolver`.
|
|
1055
|
-
*
|
|
1056
|
-
* Resolves a `FileNode` by combining name resolution (`resolver.default`) with
|
|
1057
|
-
* path resolution (`resolver.resolvePath`). The resolved file always has empty
|
|
1058
|
-
* `sources`, `imports`, and `exports` arrays, which consumers populate separately.
|
|
1059
|
-
*
|
|
1060
|
-
* In `mode: 'file'` the name is omitted and the file sits directly at the output path.
|
|
1061
|
-
*
|
|
1062
|
-
* @example Resolve a schema file
|
|
1063
|
-
* ```ts
|
|
1064
|
-
* const file = defaultResolveFile.call(
|
|
1065
|
-
* resolver,
|
|
1066
|
-
* { name: 'pet', extname: '.ts' },
|
|
1067
|
-
* { root: '/src', output: { path: 'types' } },
|
|
1068
|
-
* )
|
|
1069
|
-
* // → { baseName: 'pet.ts', path: '/src/types/pet.ts', sources: [], ... }
|
|
1070
|
-
* ```
|
|
1071
|
-
*
|
|
1072
|
-
* @example Resolve an operation file with tag grouping
|
|
1073
|
-
* ```ts
|
|
1074
|
-
* const file = defaultResolveFile.call(
|
|
1075
|
-
* resolver,
|
|
1076
|
-
* { name: 'listPets', extname: '.ts', tag: 'pets' },
|
|
1077
|
-
* { root: '/src', output: { path: 'types' }, group: { type: 'tag' } },
|
|
1078
|
-
* )
|
|
1079
|
-
* // → { baseName: 'listPets.ts', path: '/src/types/pets/listPets.ts', ... }
|
|
1080
|
-
* ```
|
|
1081
|
-
*/
|
|
1082
|
-
function defaultResolveFile({ name, extname, tag, path: groupPath }, context) {
|
|
1083
|
-
const baseName = `${(context.output.mode ?? "directory") === "file" ? "" : this.default(name, "file")}${extname}`;
|
|
1084
|
-
const filePath = this.resolvePath({
|
|
1085
|
-
baseName,
|
|
1086
|
-
tag,
|
|
1087
|
-
path: groupPath
|
|
1088
|
-
}, context);
|
|
1089
|
-
return ast.factory.createFile({
|
|
1090
|
-
path: filePath,
|
|
1091
|
-
baseName: path.basename(filePath),
|
|
1092
|
-
meta: { pluginName: this.pluginName },
|
|
1093
|
-
sources: [],
|
|
1094
|
-
imports: [],
|
|
1095
|
-
exports: []
|
|
1096
|
-
});
|
|
1097
|
-
}
|
|
1098
|
-
/**
|
|
1099
|
-
* Generates the default "Generated by Kubb" banner from config and optional node metadata.
|
|
1100
|
-
*/
|
|
1101
|
-
function buildDefaultBanner({ title, description, version, config }) {
|
|
1102
|
-
try {
|
|
1103
|
-
const source = (() => {
|
|
1104
|
-
if (Array.isArray(config.input)) {
|
|
1105
|
-
const first = config.input[0];
|
|
1106
|
-
if (first && "path" in first) return path.basename(first.path);
|
|
1107
|
-
return "";
|
|
859
|
+
if (schemaDef.is(node)) {
|
|
860
|
+
if (exclude.some((filter) => Resolver.#matchesSchema(node, filter) === true)) return null;
|
|
861
|
+
if (include) {
|
|
862
|
+
const applicable = include.map((filter) => Resolver.#matchesSchema(node, filter)).filter((result) => result !== null);
|
|
863
|
+
if (applicable.length > 0 && !applicable.includes(true)) return null;
|
|
1108
864
|
}
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
let banner = "/**\n* Generated by Kubb (https://kubb.dev/).\n* Do not edit manually.\n";
|
|
1114
|
-
if (config.output.defaultBanner === "simple") {
|
|
1115
|
-
banner += "*/\n";
|
|
1116
|
-
return banner;
|
|
865
|
+
return {
|
|
866
|
+
...options,
|
|
867
|
+
...override.find((filter) => Resolver.#matchesSchema(node, filter) === true)?.options
|
|
868
|
+
};
|
|
1117
869
|
}
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
870
|
+
return options;
|
|
871
|
+
}
|
|
872
|
+
/**
|
|
873
|
+
* Applies include/exclude filters and merges matching override options, caching the result
|
|
874
|
+
* per `(options, node)` pair. Returns `null` when the node is filtered out.
|
|
875
|
+
*/
|
|
876
|
+
#resolveOptions(node, context) {
|
|
877
|
+
const { options } = context;
|
|
878
|
+
if (typeof options !== "object" || options === null) return Resolver.#computeOptions(node, context);
|
|
879
|
+
let byOptions = Resolver.#optionsCache.get(options);
|
|
880
|
+
if (!byOptions) {
|
|
881
|
+
byOptions = /* @__PURE__ */ new WeakMap();
|
|
882
|
+
Resolver.#optionsCache.set(options, byOptions);
|
|
1123
883
|
}
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
return
|
|
884
|
+
const cached = byOptions.get(node);
|
|
885
|
+
if (cached) return cached.value;
|
|
886
|
+
const result = Resolver.#computeOptions(node, context);
|
|
887
|
+
byOptions.set(node, { value: result });
|
|
888
|
+
return result;
|
|
1129
889
|
}
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
*
|
|
1133
|
-
*
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
*
|
|
1143
|
-
*
|
|
1144
|
-
*
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
*
|
|
1162
|
-
*
|
|
1163
|
-
*
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
*
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
890
|
+
/**
|
|
891
|
+
* A custom `group.name` wins; otherwise `tag` groups use the camelCased tag and `path`
|
|
892
|
+
* groups use the first non-traversal segment (`''` when none remain, placing the file in
|
|
893
|
+
* the output root, kept safe by the caller's boundary check).
|
|
894
|
+
*/
|
|
895
|
+
static #resolveGroupDir(group, groupValue) {
|
|
896
|
+
if (group.name) return group.name({ group: groupValue });
|
|
897
|
+
if (group.type === "tag") return camelCase(groupValue);
|
|
898
|
+
const segment = groupValue.split("/").filter((part) => part !== "" && part !== "." && part !== "..")[0];
|
|
899
|
+
return segment ? camelCase(segment) : "";
|
|
900
|
+
}
|
|
901
|
+
/**
|
|
902
|
+
* `mode: 'file'` resolves directly to `output.path`. `mode: 'directory'` (default) resolves
|
|
903
|
+
* to `output.path/{baseName}`, or into a subdirectory when `group` and a `tag`/`path` value
|
|
904
|
+
* are provided.
|
|
905
|
+
*/
|
|
906
|
+
#resolvePath({ baseName, tag, path: groupPath }, { root, output, group }) {
|
|
907
|
+
if (output.mode === "file") return path.resolve(root, output.path);
|
|
908
|
+
const outputDir = path.resolve(root, output.path);
|
|
909
|
+
const result = group && (groupPath || tag) ? path.resolve(outputDir, Resolver.#resolveGroupDir(group, group.type === "path" ? groupPath : tag), baseName) : path.resolve(outputDir, baseName);
|
|
910
|
+
const outputDirWithSep = outputDir.endsWith(path.sep) ? outputDir : `${outputDir}${path.sep}`;
|
|
911
|
+
if (result !== outputDir && !result.startsWith(outputDirWithSep)) throw new Diagnostics.Error({
|
|
912
|
+
code: Diagnostics.code.pathTraversal,
|
|
913
|
+
severity: "error",
|
|
914
|
+
message: `Resolved path "${result}" is outside the output directory "${outputDir}".`,
|
|
915
|
+
help: "This can stem from a path traversal in the OpenAPI specification or a misconfigured `group.name` function. Keep generated paths within the output directory.",
|
|
916
|
+
location: { kind: "config" }
|
|
917
|
+
});
|
|
918
|
+
return result;
|
|
919
|
+
}
|
|
920
|
+
/**
|
|
921
|
+
* Builds a `FileNode` by combining file-name casing (`params.resolveName`) with path
|
|
922
|
+
* resolution. The resolved file starts with empty `sources`, `imports`, and `exports`,
|
|
923
|
+
* which consumers populate separately.
|
|
924
|
+
*/
|
|
925
|
+
#resolveFile({ name, extname, tag, path: groupPath, resolveName = toFilePath }, context) {
|
|
926
|
+
const resolvedName = context.output.mode === "file" ? "" : resolveName(name);
|
|
927
|
+
const filePath = this.#resolvePath({
|
|
928
|
+
baseName: `${resolvedName}${extname}`,
|
|
929
|
+
tag,
|
|
930
|
+
path: groupPath
|
|
931
|
+
}, context);
|
|
932
|
+
return ast.factory.createFile({
|
|
933
|
+
path: filePath,
|
|
934
|
+
baseName: path.basename(filePath),
|
|
935
|
+
meta: { pluginName: this.pluginName },
|
|
936
|
+
sources: [],
|
|
937
|
+
imports: [],
|
|
938
|
+
exports: []
|
|
939
|
+
});
|
|
940
|
+
}
|
|
941
|
+
/**
|
|
942
|
+
* Missing fields default to empty/`false` so the `BannerMeta` shape stays stable even when
|
|
943
|
+
* a caller (e.g. the barrel plugin) has no document metadata.
|
|
944
|
+
*/
|
|
945
|
+
static #buildBannerMeta(meta, file) {
|
|
946
|
+
return {
|
|
947
|
+
title: meta?.title,
|
|
948
|
+
description: meta?.description,
|
|
949
|
+
version: meta?.version,
|
|
950
|
+
baseURL: meta?.baseURL,
|
|
951
|
+
circularNames: meta?.circularNames ?? [],
|
|
952
|
+
enumNames: meta?.enumNames ?? [],
|
|
953
|
+
filePath: file?.path ?? "",
|
|
954
|
+
baseName: file?.baseName ?? "",
|
|
955
|
+
isBarrel: file?.isBarrel ?? false,
|
|
956
|
+
isAggregation: file?.isAggregation ?? false
|
|
957
|
+
};
|
|
958
|
+
}
|
|
959
|
+
/**
|
|
960
|
+
* Resolves a user-configured banner/footer value. `undefined` means not configured.
|
|
961
|
+
*/
|
|
962
|
+
static #resolveUserText(value, meta, file) {
|
|
963
|
+
if (typeof value === "function") return value(Resolver.#buildBannerMeta(meta, file));
|
|
964
|
+
if (typeof value === "string") return value;
|
|
965
|
+
}
|
|
966
|
+
static #buildDefaultBanner({ title, version, config }) {
|
|
967
|
+
const lines = [
|
|
968
|
+
"/**",
|
|
969
|
+
"* Generated by Kubb (https://kubb.dev/).",
|
|
970
|
+
"* Do not edit manually."
|
|
971
|
+
];
|
|
972
|
+
if (config.output.defaultBanner !== "simple") {
|
|
973
|
+
const input = Array.isArray(config.input) ? config.input[0] : config.input;
|
|
974
|
+
const source = input && "path" in input ? path.basename(input.path) : input && "data" in input ? "text content" : "";
|
|
975
|
+
if (source) lines.push(`* Source: ${source}`);
|
|
976
|
+
if (title) lines.push(`* Title: ${title}`);
|
|
977
|
+
if (version) lines.push(`* OpenAPI spec version: ${version}`);
|
|
978
|
+
}
|
|
979
|
+
return `${lines.join("\n")}\n*/\n`;
|
|
980
|
+
}
|
|
981
|
+
/**
|
|
982
|
+
* A user-supplied `output.banner` overrides the default Kubb notice. When
|
|
983
|
+
* `config.output.defaultBanner` is `false` and no user banner is set, returns `null`.
|
|
984
|
+
*/
|
|
985
|
+
#resolveBanner(meta, { output, config, file }) {
|
|
986
|
+
const userBanner = Resolver.#resolveUserText(output?.banner, meta, file);
|
|
987
|
+
if (userBanner !== void 0) return userBanner;
|
|
988
|
+
if (config.output.defaultBanner === false) return null;
|
|
989
|
+
return Resolver.#buildDefaultBanner({
|
|
990
|
+
title: meta?.title,
|
|
991
|
+
version: meta?.version,
|
|
992
|
+
config
|
|
993
|
+
});
|
|
994
|
+
}
|
|
995
|
+
#resolveFooter(meta, { output, file }) {
|
|
996
|
+
return Resolver.#resolveUserText(output?.footer, meta, file) ?? null;
|
|
997
|
+
}
|
|
998
|
+
};
|
|
999
|
+
//#endregion
|
|
1000
|
+
//#region src/createResolver.ts
|
|
1213
1001
|
/**
|
|
1214
|
-
* Defines a plugin resolver
|
|
1215
|
-
*
|
|
1216
|
-
*
|
|
1217
|
-
*
|
|
1218
|
-
*
|
|
1219
|
-
* - `default` sets the name casing strategy (camelCase or PascalCase).
|
|
1220
|
-
* - `resolveOptions` does include/exclude/override filtering.
|
|
1221
|
-
* - `resolvePath` computes the output path.
|
|
1222
|
-
* - `resolveFile` builds the full `FileNode`.
|
|
1223
|
-
* - `resolveBanner` and `resolveFooter` produce the top and bottom of file text.
|
|
1224
|
-
*
|
|
1225
|
-
* Methods in the returned object can call sibling resolver methods via `this`.
|
|
1226
|
-
* A custom rule can delegate to a default, for example `this.default(name, 'type')`.
|
|
1002
|
+
* Defines a plugin resolver, the object that decides what every generated symbol and file
|
|
1003
|
+
* path is called. Override the top-level `name` and `file` to set the plugin's conventions,
|
|
1004
|
+
* and add your own naming helpers, top-level (`typeName`, …) or grouped in namespaces
|
|
1005
|
+
* (`query`, `schema`, …). Every method reaches sibling helpers and the built-in machinery
|
|
1006
|
+
* through `this.name`, `this.file`, and `this.default`.
|
|
1227
1007
|
*
|
|
1228
|
-
* @example
|
|
1008
|
+
* @example Custom identifier and file casing
|
|
1229
1009
|
* ```ts
|
|
1230
|
-
* export const resolverTs =
|
|
1231
|
-
*
|
|
1232
|
-
*
|
|
1233
|
-
* return
|
|
1010
|
+
* export const resolverTs = createResolver<PluginTs>({
|
|
1011
|
+
* pluginName: 'plugin-ts',
|
|
1012
|
+
* name(name) {
|
|
1013
|
+
* return ensureValidVarName(pascalCase(name))
|
|
1234
1014
|
* },
|
|
1235
|
-
*
|
|
1236
|
-
* return this.default(name,
|
|
1015
|
+
* file(params, context) {
|
|
1016
|
+
* return this.default.file({ ...params, resolveName: (name) => toFilePath(name, pascalCase) }, context)
|
|
1237
1017
|
* },
|
|
1238
|
-
* })
|
|
1239
|
-
* ```
|
|
1240
|
-
*
|
|
1241
|
-
* @example Custom output path
|
|
1242
|
-
* ```ts
|
|
1243
|
-
* import path from 'node:path'
|
|
1244
|
-
*
|
|
1245
|
-
* export const resolverTs = defineResolver<PluginTs>(() => ({
|
|
1246
|
-
* name: 'custom',
|
|
1247
|
-
* resolvePath({ baseName }, { root, output }) {
|
|
1248
|
-
* return path.resolve(root, output.path, 'generated', baseName)
|
|
1249
|
-
* },
|
|
1250
|
-
* }))
|
|
1018
|
+
* })
|
|
1251
1019
|
* ```
|
|
1252
1020
|
*/
|
|
1253
|
-
function
|
|
1254
|
-
|
|
1255
|
-
resolver = {
|
|
1256
|
-
default: defaultResolver,
|
|
1257
|
-
resolveOptions: defaultResolveOptions,
|
|
1258
|
-
resolvePath: defaultResolvePath,
|
|
1259
|
-
resolveFile: (params, context) => defaultResolveFile.call(resolver, params, context),
|
|
1260
|
-
resolveBanner: defaultResolveBanner,
|
|
1261
|
-
resolveFooter: defaultResolveFooter,
|
|
1262
|
-
...build()
|
|
1263
|
-
};
|
|
1264
|
-
return resolver;
|
|
1021
|
+
function createResolver(options) {
|
|
1022
|
+
return new Resolver(options);
|
|
1265
1023
|
}
|
|
1266
1024
|
//#endregion
|
|
1267
1025
|
//#region src/Transform.ts
|
|
@@ -1282,12 +1040,6 @@ var Transform = class {
|
|
|
1282
1040
|
#composed = /* @__PURE__ */ new Map();
|
|
1283
1041
|
#memo = /* @__PURE__ */ new Map();
|
|
1284
1042
|
/**
|
|
1285
|
-
* Number of plugins with at least one registered macro.
|
|
1286
|
-
*/
|
|
1287
|
-
get size() {
|
|
1288
|
-
return this.#macros.size;
|
|
1289
|
-
}
|
|
1290
|
-
/**
|
|
1291
1043
|
* Appends `macro` to the plugin's list, after any macros already registered.
|
|
1292
1044
|
*/
|
|
1293
1045
|
add(pluginName, macro) {
|
|
@@ -1304,12 +1056,6 @@ var Transform = class {
|
|
|
1304
1056
|
this.#invalidate(pluginName);
|
|
1305
1057
|
}
|
|
1306
1058
|
/**
|
|
1307
|
-
* Looks up the composed visitor for `pluginName`, or `undefined` when the plugin has no macros.
|
|
1308
|
-
*/
|
|
1309
|
-
get(pluginName) {
|
|
1310
|
-
return this.#visitorFor(pluginName);
|
|
1311
|
-
}
|
|
1312
|
-
/**
|
|
1313
1059
|
* Runs the plugin's macros on `node`. Returns the original node reference when the plugin has no
|
|
1314
1060
|
* macros, so callers can compare by identity to detect a no-op.
|
|
1315
1061
|
*/
|
|
@@ -1353,20 +1099,21 @@ var Transform = class {
|
|
|
1353
1099
|
};
|
|
1354
1100
|
//#endregion
|
|
1355
1101
|
//#region src/KubbDriver.ts
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1102
|
+
const ENFORCE_ORDER = {
|
|
1103
|
+
pre: -1,
|
|
1104
|
+
post: 1
|
|
1105
|
+
};
|
|
1106
|
+
const enforceWeight = (plugin) => plugin.enforce ? ENFORCE_ORDER[plugin.enforce] : 0;
|
|
1359
1107
|
var KubbDriver = class {
|
|
1360
1108
|
config;
|
|
1361
1109
|
options;
|
|
1362
1110
|
/**
|
|
1363
|
-
* The
|
|
1364
|
-
* Parse-only adapters are wrapped automatically.
|
|
1111
|
+
* The `InputNode` produced by the adapter. Set after adapter setup.
|
|
1365
1112
|
*/
|
|
1366
1113
|
inputNode = null;
|
|
1367
1114
|
adapter = null;
|
|
1368
1115
|
/**
|
|
1369
|
-
* Raw adapter source so `adapter.parse()`
|
|
1116
|
+
* Raw adapter source so `adapter.parse()` can run lazily.
|
|
1370
1117
|
* Intentionally outlives the build, cleared by `dispose()`.
|
|
1371
1118
|
*/
|
|
1372
1119
|
#adapterSource = null;
|
|
@@ -1378,17 +1125,17 @@ var KubbDriver = class {
|
|
|
1378
1125
|
fileManager = new FileManager();
|
|
1379
1126
|
plugins = /* @__PURE__ */ new Map();
|
|
1380
1127
|
/**
|
|
1381
|
-
* Tracks which plugins have generators registered via `addGenerator()` (
|
|
1382
|
-
* Used by the build loop to decide whether to emit generator
|
|
1128
|
+
* Tracks which plugins have generators registered via `addGenerator()` (hook-based path).
|
|
1129
|
+
* Used by the build loop to decide whether to emit generator hooks for a given plugin.
|
|
1383
1130
|
*/
|
|
1384
|
-
#
|
|
1131
|
+
#hookGeneratorPlugins = /* @__PURE__ */ new Set();
|
|
1385
1132
|
#resolvers = /* @__PURE__ */ new Map();
|
|
1386
1133
|
#defaultResolvers = /* @__PURE__ */ new Map();
|
|
1387
1134
|
/**
|
|
1388
|
-
*
|
|
1389
|
-
* in one pass. External `hooks.
|
|
1135
|
+
* Removers for every listener the driver added (plugin, generator) so `dispose()` can detach
|
|
1136
|
+
* them in one pass. External `hooks.hook(...)` listeners are not tracked.
|
|
1390
1137
|
*/
|
|
1391
|
-
#
|
|
1138
|
+
#unhooks = [];
|
|
1392
1139
|
/**
|
|
1393
1140
|
* Transform registry. Plugins populate it during `kubb:plugin:setup` via `addMacro`/`setMacros`,
|
|
1394
1141
|
* and `#runGenerators` reads it once per `(plugin, node)` pair through `applyTo`.
|
|
@@ -1400,163 +1147,137 @@ var KubbDriver = class {
|
|
|
1400
1147
|
this.adapter = config.adapter ?? null;
|
|
1401
1148
|
}
|
|
1402
1149
|
/**
|
|
1403
|
-
* Attaches a listener to the shared emitter and tracks it so `dispose()` can remove it later.
|
|
1404
|
-
* Listeners attached directly via `hooks.on(...)` are not tracked and survive disposal.
|
|
1405
|
-
*/
|
|
1406
|
-
#trackListener(event, handler) {
|
|
1407
|
-
this.hooks.on(event, handler);
|
|
1408
|
-
this.#listeners.push([event, handler]);
|
|
1409
|
-
}
|
|
1410
|
-
/**
|
|
1411
1150
|
* Normalizes every configured plugin, orders them, and registers their lifecycle handlers.
|
|
1412
1151
|
* A plugin that another lists as a dependency runs first, then `enforce: 'pre'` before
|
|
1413
1152
|
* `'post'`. When the config has an adapter, the adapter source is resolved from the input
|
|
1414
1153
|
* so `run` can parse it later.
|
|
1415
1154
|
*/
|
|
1416
1155
|
async setup() {
|
|
1417
|
-
const normalized = this.config.plugins.map((rawPlugin) =>
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1156
|
+
const normalized = this.#sortPlugins(this.config.plugins.map((rawPlugin) => {
|
|
1157
|
+
return {
|
|
1158
|
+
name: rawPlugin.name,
|
|
1159
|
+
dependencies: rawPlugin.dependencies,
|
|
1160
|
+
enforce: rawPlugin.enforce,
|
|
1161
|
+
hooks: rawPlugin.hooks,
|
|
1162
|
+
options: rawPlugin.options ?? {
|
|
1163
|
+
output: {
|
|
1164
|
+
path: ".",
|
|
1165
|
+
mode: "directory"
|
|
1166
|
+
},
|
|
1167
|
+
exclude: [],
|
|
1168
|
+
override: []
|
|
1169
|
+
}
|
|
1170
|
+
};
|
|
1171
|
+
}));
|
|
1424
1172
|
for (const plugin of normalized) {
|
|
1425
|
-
if (plugin.apply) plugin.apply(this.config);
|
|
1426
1173
|
this.#registerPlugin(plugin);
|
|
1427
1174
|
this.plugins.set(plugin.name, plugin);
|
|
1428
1175
|
}
|
|
1429
1176
|
if (this.config.adapter) this.#adapterSource = inputToAdapterSource(this.config);
|
|
1430
1177
|
}
|
|
1178
|
+
/**
|
|
1179
|
+
* Orders plugins so every dependency runs before its dependents (Kahn's algorithm), with
|
|
1180
|
+
* `enforce` (`'pre'` before normal before `'post'`) and declaration order as tiebreaks.
|
|
1181
|
+
* A pairwise `Array.sort` comparator cannot do this: dependency relations are not transitive
|
|
1182
|
+
* at the comparator level, so a chain where A depends on B and B depends on C could come out
|
|
1183
|
+
* wrong when A and C are never compared directly. Dependencies on plugins missing from the
|
|
1184
|
+
* config are ignored here and surface later through `requirePlugin`.
|
|
1185
|
+
*/
|
|
1186
|
+
#sortPlugins(plugins) {
|
|
1187
|
+
const queue = [...plugins].sort((a, b) => enforceWeight(a) - enforceWeight(b));
|
|
1188
|
+
const names = new Set(queue.map((plugin) => plugin.name));
|
|
1189
|
+
const blockedBy = new Map(queue.map((plugin) => [plugin.name, new Set(plugin.dependencies?.filter((name) => names.has(name) && name !== plugin.name))]));
|
|
1190
|
+
const sorted = [];
|
|
1191
|
+
for (const _ of plugins) {
|
|
1192
|
+
const index = queue.findIndex((plugin) => blockedBy.get(plugin.name)?.size === 0);
|
|
1193
|
+
if (index === -1) throw new Diagnostics.Error({
|
|
1194
|
+
code: Diagnostics.code.invalidPluginOptions,
|
|
1195
|
+
severity: "error",
|
|
1196
|
+
message: `Plugin dependencies form a cycle: ${queue.map((plugin) => plugin.name).join(" → ")}.`,
|
|
1197
|
+
help: "Remove one of the `dependencies` entries so the plugins can be ordered.",
|
|
1198
|
+
location: { kind: "config" }
|
|
1199
|
+
});
|
|
1200
|
+
const [plugin] = queue.splice(index, 1);
|
|
1201
|
+
if (!plugin) break;
|
|
1202
|
+
sorted.push(plugin);
|
|
1203
|
+
for (const blockers of blockedBy.values()) blockers.delete(plugin.name);
|
|
1204
|
+
}
|
|
1205
|
+
return sorted;
|
|
1206
|
+
}
|
|
1431
1207
|
get hooks() {
|
|
1432
1208
|
return this.options.hooks;
|
|
1433
1209
|
}
|
|
1434
1210
|
/**
|
|
1435
|
-
* Builds a `NormalizedPlugin` from a hook-style plugin, filling in default
|
|
1436
|
-
* options and copying `apply` when present. Registering its lifecycle handlers
|
|
1437
|
-
* on the `AsyncEventEmitter` is done separately by `#registerPlugin`.
|
|
1438
|
-
*/
|
|
1439
|
-
#normalizePlugin(plugin) {
|
|
1440
|
-
const normalized = {
|
|
1441
|
-
name: plugin.name,
|
|
1442
|
-
dependencies: plugin.dependencies,
|
|
1443
|
-
enforce: plugin.enforce,
|
|
1444
|
-
hooks: plugin.hooks,
|
|
1445
|
-
options: plugin.options ?? {
|
|
1446
|
-
output: {
|
|
1447
|
-
path: ".",
|
|
1448
|
-
mode: "directory"
|
|
1449
|
-
},
|
|
1450
|
-
exclude: [],
|
|
1451
|
-
override: []
|
|
1452
|
-
}
|
|
1453
|
-
};
|
|
1454
|
-
if ("apply" in plugin && typeof plugin.apply === "function") normalized.apply = plugin.apply;
|
|
1455
|
-
return normalized;
|
|
1456
|
-
}
|
|
1457
|
-
/**
|
|
1458
1211
|
* Parses the adapter source into `this.inputNode`. Idempotent, so repeated calls from
|
|
1459
|
-
* `run` do not re-parse.
|
|
1460
|
-
* Adapters with only `parse()` are wrapped via `ast.factory.createInput({ stream: true })` so the dispatch loop
|
|
1461
|
-
* stays stream-only.
|
|
1212
|
+
* `run` do not re-parse.
|
|
1462
1213
|
*/
|
|
1463
1214
|
async #parseInput() {
|
|
1464
1215
|
if (this.inputNode || !this.adapter || !this.#adapterSource) return;
|
|
1465
|
-
|
|
1466
|
-
const source = this.#adapterSource;
|
|
1467
|
-
if (adapter.stream) {
|
|
1468
|
-
this.inputNode = await adapter.stream(source);
|
|
1469
|
-
return;
|
|
1470
|
-
}
|
|
1471
|
-
const parsed = await adapter.parse(source);
|
|
1472
|
-
this.inputNode = ast.factory.createInput({
|
|
1473
|
-
stream: true,
|
|
1474
|
-
schemas: arrayToAsyncIterable(parsed.schemas),
|
|
1475
|
-
operations: arrayToAsyncIterable(parsed.operations),
|
|
1476
|
-
meta: parsed.meta
|
|
1477
|
-
});
|
|
1216
|
+
this.inputNode = await this.adapter.parse(this.#adapterSource);
|
|
1478
1217
|
}
|
|
1479
1218
|
/**
|
|
1480
|
-
* Registers a
|
|
1481
|
-
*
|
|
1482
|
-
*
|
|
1483
|
-
*
|
|
1484
|
-
* Every other `KubbHooks` event registers as a pass-through listener that external tooling
|
|
1485
|
-
* can observe via `hooks.on(...)`.
|
|
1219
|
+
* Registers a plugin's lifecycle hooks on the shared `Hookable` as pass-through listeners that
|
|
1220
|
+
* external tooling can observe via `hooks.hook(...)`. The returned remover is tracked for
|
|
1221
|
+
* `dispose`. `kubb:plugin:setup` is skipped here; `setupHooks` invokes it directly with a
|
|
1222
|
+
* plugin-scoped context.
|
|
1486
1223
|
*
|
|
1487
1224
|
* @internal
|
|
1488
1225
|
*/
|
|
1489
1226
|
#registerPlugin(plugin) {
|
|
1490
1227
|
const { hooks } = plugin;
|
|
1491
1228
|
if (!hooks) return;
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
const pluginCtx = {
|
|
1495
|
-
...globalCtx,
|
|
1496
|
-
options: plugin.options ?? {},
|
|
1497
|
-
addGenerator: (...generators) => {
|
|
1498
|
-
for (const generator of generators.flat()) this.registerGenerator(plugin.name, generator);
|
|
1499
|
-
},
|
|
1500
|
-
setResolver: (resolver) => {
|
|
1501
|
-
this.setPluginResolver(plugin.name, resolver);
|
|
1502
|
-
},
|
|
1503
|
-
addMacro: (macro) => {
|
|
1504
|
-
this.#transforms.add(plugin.name, macro);
|
|
1505
|
-
},
|
|
1506
|
-
setMacros: (macros) => {
|
|
1507
|
-
this.#transforms.set(plugin.name, macros);
|
|
1508
|
-
},
|
|
1509
|
-
setOptions: (opts) => {
|
|
1510
|
-
plugin.options = {
|
|
1511
|
-
...plugin.options,
|
|
1512
|
-
...opts
|
|
1513
|
-
};
|
|
1514
|
-
if (plugin.options.output) {
|
|
1515
|
-
const group = "group" in plugin.options ? plugin.options.group : void 0;
|
|
1516
|
-
plugin.options.output = normalizeOutput({
|
|
1517
|
-
output: plugin.options.output,
|
|
1518
|
-
group,
|
|
1519
|
-
pluginName: plugin.name
|
|
1520
|
-
});
|
|
1521
|
-
}
|
|
1522
|
-
},
|
|
1523
|
-
injectFile: (userFileNode) => {
|
|
1524
|
-
this.fileManager.add(ast.factory.createFile(userFileNode));
|
|
1525
|
-
}
|
|
1526
|
-
};
|
|
1527
|
-
return hooks["kubb:plugin:setup"](pluginCtx);
|
|
1528
|
-
};
|
|
1529
|
-
this.#trackListener("kubb:plugin:setup", setupHandler);
|
|
1530
|
-
}
|
|
1531
|
-
for (const event of Object.keys(hooks)) {
|
|
1532
|
-
if (event === "kubb:plugin:setup") continue;
|
|
1533
|
-
const handler = hooks[event];
|
|
1534
|
-
if (!handler) continue;
|
|
1535
|
-
this.#trackListener(event, handler);
|
|
1536
|
-
}
|
|
1229
|
+
const { "kubb:plugin:setup": _setup, ...configHooks } = hooks;
|
|
1230
|
+
this.#unhooks.push(this.hooks.addHooks(configHooks));
|
|
1537
1231
|
}
|
|
1538
1232
|
/**
|
|
1539
|
-
*
|
|
1540
|
-
*
|
|
1541
|
-
*
|
|
1542
|
-
*
|
|
1233
|
+
* Runs each plugin's `kubb:plugin:setup` handler, in plugin order, with a context scoped to that
|
|
1234
|
+
* plugin so `addGenerator`, `setResolver`, `addMacro`, `setMacros`, and `setOptions` target its
|
|
1235
|
+
* `NormalizedPlugin` entry. Called once from `run` before the plugin execution loop begins, so
|
|
1236
|
+
* plugins can configure generators, resolvers, macros, and options before `buildStart`.
|
|
1543
1237
|
*/
|
|
1544
|
-
async
|
|
1238
|
+
async setupHooks() {
|
|
1545
1239
|
const noop = () => {};
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1240
|
+
for (const plugin of this.plugins.values()) {
|
|
1241
|
+
const setup = plugin.hooks?.["kubb:plugin:setup"];
|
|
1242
|
+
if (!setup) continue;
|
|
1243
|
+
await setup({
|
|
1244
|
+
config: this.config,
|
|
1245
|
+
options: plugin.options ?? {},
|
|
1246
|
+
updateConfig: noop,
|
|
1247
|
+
addGenerator: (...generators) => {
|
|
1248
|
+
for (const generator of generators) this.registerGenerator(plugin.name, generator);
|
|
1249
|
+
},
|
|
1250
|
+
setResolver: (resolver) => {
|
|
1251
|
+
this.setPluginResolver(plugin.name, resolver);
|
|
1252
|
+
},
|
|
1253
|
+
addMacro: (macro) => {
|
|
1254
|
+
this.#transforms.add(plugin.name, macro);
|
|
1255
|
+
},
|
|
1256
|
+
setMacros: (macros) => {
|
|
1257
|
+
this.#transforms.set(plugin.name, macros);
|
|
1258
|
+
},
|
|
1259
|
+
setOptions: (opts) => {
|
|
1260
|
+
plugin.options = {
|
|
1261
|
+
...plugin.options,
|
|
1262
|
+
...opts
|
|
1263
|
+
};
|
|
1264
|
+
if (plugin.options.output) {
|
|
1265
|
+
const group = "group" in plugin.options ? plugin.options.group : void 0;
|
|
1266
|
+
plugin.options.output = normalizeOutput({
|
|
1267
|
+
output: plugin.options.output,
|
|
1268
|
+
group,
|
|
1269
|
+
pluginName: plugin.name
|
|
1270
|
+
});
|
|
1271
|
+
}
|
|
1272
|
+
},
|
|
1273
|
+
injectFile: (userFileNode) => {
|
|
1274
|
+
this.fileManager.add(ast.factory.createFile(userFileNode));
|
|
1275
|
+
}
|
|
1276
|
+
});
|
|
1277
|
+
}
|
|
1557
1278
|
}
|
|
1558
1279
|
/**
|
|
1559
|
-
* Registers a generator for the given plugin on the shared
|
|
1280
|
+
* Registers a generator for the given plugin on the shared hook emitter.
|
|
1560
1281
|
*
|
|
1561
1282
|
* The generator's `schema`, `operation`, and `operations` methods are registered as
|
|
1562
1283
|
* listeners on `kubb:generate:schema`, `kubb:generate:operation`, and `kubb:generate:operations`
|
|
@@ -1569,9 +1290,9 @@ var KubbDriver = class {
|
|
|
1569
1290
|
* Call this method inside `addGenerator()` (in `kubb:plugin:setup`) to wire up a generator.
|
|
1570
1291
|
*/
|
|
1571
1292
|
registerGenerator(pluginName, generator) {
|
|
1572
|
-
const
|
|
1573
|
-
if (!method) return;
|
|
1574
|
-
|
|
1293
|
+
const wrap = (method) => {
|
|
1294
|
+
if (!method) return void 0;
|
|
1295
|
+
return async (node, ctx) => {
|
|
1575
1296
|
if (ctx.plugin.name !== pluginName) return;
|
|
1576
1297
|
const result = await method(node, ctx);
|
|
1577
1298
|
await this.dispatch({
|
|
@@ -1579,22 +1300,23 @@ var KubbDriver = class {
|
|
|
1579
1300
|
renderer: generator.renderer
|
|
1580
1301
|
});
|
|
1581
1302
|
};
|
|
1582
|
-
this.#trackListener(event, handler);
|
|
1583
1303
|
};
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1304
|
+
this.#unhooks.push(this.hooks.addHooks({
|
|
1305
|
+
"kubb:generate:schema": wrap(generator.schema),
|
|
1306
|
+
"kubb:generate:operation": wrap(generator.operation),
|
|
1307
|
+
"kubb:generate:operations": wrap(generator.operations)
|
|
1308
|
+
}));
|
|
1309
|
+
this.#hookGeneratorPlugins.add(pluginName);
|
|
1588
1310
|
}
|
|
1589
1311
|
/**
|
|
1590
1312
|
* Returns `true` when at least one generator was registered for the given plugin
|
|
1591
|
-
* via `addGenerator()` in `kubb:plugin:setup
|
|
1313
|
+
* via `addGenerator()` in `kubb:plugin:setup`.
|
|
1592
1314
|
*
|
|
1593
|
-
* Used by the build loop to decide whether to walk the AST and emit generator
|
|
1594
|
-
* for a plugin
|
|
1315
|
+
* Used by the build loop to decide whether to walk the AST and emit generator hooks
|
|
1316
|
+
* for a plugin.
|
|
1595
1317
|
*/
|
|
1596
|
-
|
|
1597
|
-
return this.#
|
|
1318
|
+
hasHookGenerators(pluginName) {
|
|
1319
|
+
return this.#hookGeneratorPlugins.has(pluginName);
|
|
1598
1320
|
}
|
|
1599
1321
|
/**
|
|
1600
1322
|
* Runs the full plugin pipeline. Returns the diagnostics collected so far even
|
|
@@ -1603,40 +1325,36 @@ var KubbDriver = class {
|
|
|
1603
1325
|
* contributes a `timing` diagnostic for the run summary.
|
|
1604
1326
|
*/
|
|
1605
1327
|
async run({ storage }) {
|
|
1606
|
-
const { hooks, config } = this;
|
|
1328
|
+
const { hooks, config, fileManager } = this;
|
|
1607
1329
|
const diagnostics = [];
|
|
1608
1330
|
const parsersMap = /* @__PURE__ */ new Map();
|
|
1609
1331
|
for (const parser of config.parsers) if (parser.extNames) for (const ext of parser.extNames) parsersMap.set(ext, parser);
|
|
1610
|
-
const
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
extension: config.output.extension
|
|
1614
|
-
});
|
|
1615
|
-
processor.hooks.on("start", async (files) => {
|
|
1616
|
-
await hooks.emit("kubb:files:processing:start", { files });
|
|
1617
|
-
});
|
|
1332
|
+
const onWriteStart = async (files) => {
|
|
1333
|
+
await hooks.callHook("kubb:files:processing:start", { files });
|
|
1334
|
+
};
|
|
1618
1335
|
const updateBuffer = [];
|
|
1619
|
-
|
|
1336
|
+
const onWriteUpdate = (item) => {
|
|
1620
1337
|
updateBuffer.push(item);
|
|
1621
|
-
}
|
|
1622
|
-
|
|
1623
|
-
await hooks.
|
|
1338
|
+
};
|
|
1339
|
+
const onWriteEnd = async (files) => {
|
|
1340
|
+
await hooks.callHook("kubb:files:processing:update", { files: updateBuffer.map((item) => ({
|
|
1624
1341
|
...item,
|
|
1625
1342
|
config
|
|
1626
1343
|
})) });
|
|
1627
1344
|
updateBuffer.length = 0;
|
|
1628
|
-
await hooks.
|
|
1629
|
-
});
|
|
1630
|
-
const onFileUpsert = (file) => {
|
|
1631
|
-
processor.enqueue(file);
|
|
1345
|
+
await hooks.callHook("kubb:files:processing:end", { files });
|
|
1632
1346
|
};
|
|
1633
|
-
|
|
1347
|
+
const unhookWrites = fileManager.hooks.addHooks({
|
|
1348
|
+
start: onWriteStart,
|
|
1349
|
+
update: onWriteUpdate,
|
|
1350
|
+
end: onWriteEnd
|
|
1351
|
+
});
|
|
1634
1352
|
return Diagnostics.scope((diagnostic) => diagnostics.push(diagnostic), async () => {
|
|
1635
1353
|
try {
|
|
1636
1354
|
const outputRoot = resolve(config.root, config.output.path);
|
|
1637
1355
|
await this.#parseInput();
|
|
1638
|
-
await this.
|
|
1639
|
-
if (this.adapter && this.inputNode) await hooks.
|
|
1356
|
+
await this.setupHooks();
|
|
1357
|
+
if (this.adapter && this.inputNode) await hooks.callHook("kubb:build:start", Object.assign({
|
|
1640
1358
|
config,
|
|
1641
1359
|
adapter: this.adapter,
|
|
1642
1360
|
meta: this.inputNode.meta,
|
|
@@ -1647,9 +1365,9 @@ var KubbDriver = class {
|
|
|
1647
1365
|
const context = this.getContext(plugin);
|
|
1648
1366
|
const hrStart = process.hrtime();
|
|
1649
1367
|
try {
|
|
1650
|
-
await hooks.
|
|
1368
|
+
await hooks.callHook("kubb:plugin:start", { plugin });
|
|
1651
1369
|
} catch (caughtError) {
|
|
1652
|
-
const error = caughtError;
|
|
1370
|
+
const error = toError(caughtError);
|
|
1653
1371
|
const duration = getElapsedMs(hrStart);
|
|
1654
1372
|
await this.#emitPluginEnd({
|
|
1655
1373
|
plugin,
|
|
@@ -1666,7 +1384,7 @@ var KubbDriver = class {
|
|
|
1666
1384
|
}));
|
|
1667
1385
|
continue;
|
|
1668
1386
|
}
|
|
1669
|
-
if (this.
|
|
1387
|
+
if (this.hasHookGenerators(plugin.name)) {
|
|
1670
1388
|
generatorPlugins.push({
|
|
1671
1389
|
plugin,
|
|
1672
1390
|
context,
|
|
@@ -1685,11 +1403,14 @@ var KubbDriver = class {
|
|
|
1685
1403
|
success: true
|
|
1686
1404
|
});
|
|
1687
1405
|
}
|
|
1688
|
-
diagnostics.push(...await this.#runGenerators(generatorPlugins
|
|
1689
|
-
await
|
|
1690
|
-
await
|
|
1691
|
-
|
|
1692
|
-
|
|
1406
|
+
diagnostics.push(...await this.#runGenerators(generatorPlugins));
|
|
1407
|
+
await hooks.callHook("kubb:plugins:end", Object.assign({ config }, this.#filesPayload()));
|
|
1408
|
+
await fileManager.write(fileManager.files, {
|
|
1409
|
+
storage,
|
|
1410
|
+
parsers: parsersMap,
|
|
1411
|
+
extension: config.output.extension
|
|
1412
|
+
});
|
|
1413
|
+
await hooks.callHook("kubb:build:end", {
|
|
1693
1414
|
files: this.fileManager.files,
|
|
1694
1415
|
config,
|
|
1695
1416
|
outputDir: outputRoot
|
|
@@ -1699,7 +1420,7 @@ var KubbDriver = class {
|
|
|
1699
1420
|
diagnostics.push(Diagnostics.from(caughtError));
|
|
1700
1421
|
return { diagnostics: Diagnostics.dedupe(diagnostics) };
|
|
1701
1422
|
} finally {
|
|
1702
|
-
|
|
1423
|
+
unhookWrites();
|
|
1703
1424
|
}
|
|
1704
1425
|
});
|
|
1705
1426
|
}
|
|
@@ -1713,7 +1434,7 @@ var KubbDriver = class {
|
|
|
1713
1434
|
};
|
|
1714
1435
|
}
|
|
1715
1436
|
#emitPluginEnd({ plugin, duration, success, error }) {
|
|
1716
|
-
return this.hooks.
|
|
1437
|
+
return this.hooks.callHook("kubb:plugin:end", Object.assign({
|
|
1717
1438
|
plugin,
|
|
1718
1439
|
duration,
|
|
1719
1440
|
success,
|
|
@@ -1722,22 +1443,21 @@ var KubbDriver = class {
|
|
|
1722
1443
|
}, this.#filesPayload()));
|
|
1723
1444
|
}
|
|
1724
1445
|
/**
|
|
1725
|
-
*
|
|
1446
|
+
* Runs schemas and operations through every plugin's generators. Each node is run
|
|
1726
1447
|
* through the plugin's macros (from `this.#transforms`) before the generator sees it,
|
|
1727
1448
|
* so plugins stay isolated and the hot path stays per-node. Schemas run before operations
|
|
1728
|
-
*
|
|
1449
|
+
* so file output stays deterministic across runs.
|
|
1729
1450
|
* A failing plugin contributes an error diagnostic so the rest of the build continues.
|
|
1730
1451
|
* Every plugin also contributes a `timing` diagnostic.
|
|
1731
1452
|
*
|
|
1732
|
-
* Plugins
|
|
1733
|
-
*
|
|
1734
|
-
*
|
|
1735
|
-
* sit at the initial value until the very end of the run.
|
|
1453
|
+
* Plugins are processed one at a time, in full, so `kubb:plugin:end` fires as each one
|
|
1454
|
+
* completes rather than all at once at the end. That ordering drives the CLI's
|
|
1455
|
+
* `Plugins N/M` counter.
|
|
1736
1456
|
*
|
|
1737
1457
|
* When `this.inputNode` is `null`, every entry still gets a `kubb:plugin:end` so
|
|
1738
1458
|
* post-plugin listeners (the barrel writer and friends) complete.
|
|
1739
1459
|
*/
|
|
1740
|
-
async #runGenerators(entries
|
|
1460
|
+
async #runGenerators(entries) {
|
|
1741
1461
|
const diagnostics = [];
|
|
1742
1462
|
if (entries.length === 0) return diagnostics;
|
|
1743
1463
|
if (!this.inputNode) {
|
|
@@ -1757,160 +1477,104 @@ var KubbDriver = class {
|
|
|
1757
1477
|
}
|
|
1758
1478
|
const transforms = this.#transforms;
|
|
1759
1479
|
const { schemas, operations } = this.inputNode;
|
|
1760
|
-
const states = entries.map(({ plugin, context, hrStart }) => {
|
|
1761
|
-
const { exclude, include, override } = plugin.options;
|
|
1762
|
-
const hasExclude = Array.isArray(exclude) && exclude.length > 0;
|
|
1763
|
-
const hasInclude = Array.isArray(include) && include.length > 0;
|
|
1764
|
-
const hasOverride = Array.isArray(override) && override.length > 0;
|
|
1765
|
-
return {
|
|
1766
|
-
plugin,
|
|
1767
|
-
generatorContext: {
|
|
1768
|
-
...context,
|
|
1769
|
-
resolver: this.getResolver(plugin.name)
|
|
1770
|
-
},
|
|
1771
|
-
generators: plugin.generators ?? [],
|
|
1772
|
-
hrStart,
|
|
1773
|
-
failed: false,
|
|
1774
|
-
error: null,
|
|
1775
|
-
optionsAreStatic: !hasExclude && !hasInclude && !hasOverride,
|
|
1776
|
-
allowedSchemaNames: null
|
|
1777
|
-
};
|
|
1778
|
-
});
|
|
1779
1480
|
const emitsSchemaHook = this.hooks.listenerCount("kubb:generate:schema") > 0;
|
|
1780
1481
|
const emitsOperationHook = this.hooks.listenerCount("kubb:generate:operation") > 0;
|
|
1781
1482
|
const emitsOperationsHook = this.hooks.listenerCount("kubb:generate:operations") > 0;
|
|
1782
|
-
const
|
|
1783
|
-
const
|
|
1784
|
-
const pruningStates = states.filter(({ plugin }) => {
|
|
1785
|
-
const { include } = plugin.options;
|
|
1786
|
-
return (include?.some(({ type }) => OPERATION_FILTER_TYPES.has(type)) ?? false) && !(include?.some(({ type }) => type === "schemaName") ?? false);
|
|
1787
|
-
});
|
|
1788
|
-
if (pruningStates.length > 0) {
|
|
1789
|
-
const includedOpsByState = new Map(pruningStates.map((state) => [state, []]));
|
|
1790
|
-
for (const operation of operationsBuffer) for (const state of pruningStates) {
|
|
1791
|
-
const { exclude, include, override } = state.plugin.options;
|
|
1792
|
-
if (state.generatorContext.resolver.resolveOptions(operation, {
|
|
1793
|
-
options: state.plugin.options,
|
|
1794
|
-
exclude,
|
|
1795
|
-
include,
|
|
1796
|
-
override
|
|
1797
|
-
}) !== null) includedOpsByState.get(state)?.push(operation);
|
|
1798
|
-
}
|
|
1799
|
-
for (const state of pruningStates) {
|
|
1800
|
-
state.allowedSchemaNames = collectUsedSchemaNames(includedOpsByState.get(state) ?? [], schemasBuffer);
|
|
1801
|
-
includedOpsByState.delete(state);
|
|
1802
|
-
}
|
|
1803
|
-
}
|
|
1804
|
-
const resolveForPlugin = (state, node) => {
|
|
1805
|
-
const { plugin, generatorContext } = state;
|
|
1806
|
-
const transformedNode = transforms.applyTo(plugin.name, node);
|
|
1807
|
-
if (state.optionsAreStatic) return {
|
|
1808
|
-
transformedNode,
|
|
1809
|
-
options: plugin.options
|
|
1810
|
-
};
|
|
1483
|
+
const allowedSchemaNamesByPlugin = /* @__PURE__ */ new Map();
|
|
1484
|
+
for (const { plugin } of entries) {
|
|
1811
1485
|
const { exclude, include, override } = plugin.options;
|
|
1812
|
-
|
|
1486
|
+
if (!((include?.some(({ type }) => OPERATION_FILTER_TYPES.has(type)) ?? false) && !(include?.some(({ type }) => type === "schemaName") ?? false))) continue;
|
|
1487
|
+
const resolver = this.getResolver(plugin.name);
|
|
1488
|
+
const includedOps = operations.filter((operation) => resolver.default.options(operation, {
|
|
1813
1489
|
options: plugin.options,
|
|
1814
1490
|
exclude,
|
|
1815
1491
|
include,
|
|
1816
1492
|
override
|
|
1817
|
-
});
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
1493
|
+
}) !== null);
|
|
1494
|
+
allowedSchemaNamesByPlugin.set(plugin.name, collectUsedSchemaNames(includedOps, schemas));
|
|
1495
|
+
}
|
|
1496
|
+
for (const { plugin, context, hrStart } of entries) {
|
|
1497
|
+
const generatorContext = {
|
|
1498
|
+
...context,
|
|
1499
|
+
resolver: this.getResolver(plugin.name)
|
|
1822
1500
|
};
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
1501
|
+
const { exclude, include, override } = plugin.options;
|
|
1502
|
+
const optionsAreStatic = !exclude?.length && !include?.length && !override?.length;
|
|
1503
|
+
const allowedSchemaNames = allowedSchemaNamesByPlugin.get(plugin.name) ?? null;
|
|
1504
|
+
let error = null;
|
|
1505
|
+
const resolveForPlugin = (node) => {
|
|
1506
|
+
const transformedNode = transforms.applyTo(plugin.name, node);
|
|
1507
|
+
if (optionsAreStatic) return {
|
|
1508
|
+
transformedNode,
|
|
1509
|
+
options: plugin.options
|
|
1510
|
+
};
|
|
1511
|
+
const options = generatorContext.resolver.default.options(transformedNode, {
|
|
1512
|
+
options: plugin.options,
|
|
1513
|
+
exclude,
|
|
1514
|
+
include,
|
|
1515
|
+
override
|
|
1516
|
+
});
|
|
1517
|
+
if (options === null) return null;
|
|
1518
|
+
return {
|
|
1519
|
+
transformedNode,
|
|
1833
1520
|
options
|
|
1834
1521
|
};
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
const
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
1522
|
+
};
|
|
1523
|
+
if (emitsSchemaHook) for (const node of schemas) {
|
|
1524
|
+
if (error) break;
|
|
1525
|
+
try {
|
|
1526
|
+
const resolved = resolveForPlugin(node);
|
|
1527
|
+
if (!resolved) continue;
|
|
1528
|
+
const { transformedNode, options } = resolved;
|
|
1529
|
+
if (allowedSchemaNames !== null && transformedNode.name && !allowedSchemaNames.has(transformedNode.name)) continue;
|
|
1530
|
+
await this.hooks.callHook("kubb:generate:schema", transformedNode, {
|
|
1531
|
+
...generatorContext,
|
|
1532
|
+
options
|
|
1843
1533
|
});
|
|
1844
|
-
|
|
1534
|
+
} catch (caughtError) {
|
|
1535
|
+
error = toError(caughtError);
|
|
1845
1536
|
}
|
|
1846
|
-
if (dispatch.emit) await dispatch.emit(transformedNode, ctx);
|
|
1847
|
-
} catch (caughtError) {
|
|
1848
|
-
state.failed = true;
|
|
1849
|
-
state.error = caughtError;
|
|
1850
1537
|
}
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
const dispatchPass = async (state, nodes, dispatch) => {
|
|
1863
|
-
let sinceFlush = 0;
|
|
1864
|
-
for (const node of nodes) {
|
|
1865
|
-
await dispatchNode(state, node, dispatch);
|
|
1866
|
-
if (++sinceFlush >= 8) {
|
|
1867
|
-
sinceFlush = 0;
|
|
1868
|
-
await flushPending();
|
|
1538
|
+
if (emitsOperationHook) for (const node of operations) {
|
|
1539
|
+
if (error) break;
|
|
1540
|
+
try {
|
|
1541
|
+
const resolved = resolveForPlugin(node);
|
|
1542
|
+
if (!resolved) continue;
|
|
1543
|
+
await this.hooks.callHook("kubb:generate:operation", resolved.transformedNode, {
|
|
1544
|
+
...generatorContext,
|
|
1545
|
+
options: resolved.options
|
|
1546
|
+
});
|
|
1547
|
+
} catch (caughtError) {
|
|
1548
|
+
error = toError(caughtError);
|
|
1869
1549
|
}
|
|
1870
1550
|
}
|
|
1871
|
-
if (
|
|
1872
|
-
};
|
|
1873
|
-
for (const state of states) {
|
|
1874
|
-
const needsOperationsAggregate = emitsOperationsHook || state.generators.some((gen) => !!gen.operations);
|
|
1875
|
-
await dispatchPass(state, schemasBuffer, schemaDispatch);
|
|
1876
|
-
await dispatchPass(state, operationsBuffer, operationDispatch);
|
|
1877
|
-
if (!state.failed && needsOperationsAggregate) try {
|
|
1878
|
-
const { plugin, generatorContext, generators } = state;
|
|
1551
|
+
if (!error && emitsOperationsHook) try {
|
|
1879
1552
|
const ctx = {
|
|
1880
1553
|
...generatorContext,
|
|
1881
1554
|
options: plugin.options
|
|
1882
1555
|
};
|
|
1883
|
-
const pluginOperations =
|
|
1884
|
-
const resolved = resolveForPlugin(
|
|
1556
|
+
const pluginOperations = operations.reduce((acc, node) => {
|
|
1557
|
+
const resolved = resolveForPlugin(node);
|
|
1885
1558
|
if (resolved) acc.push(resolved.transformedNode);
|
|
1886
1559
|
return acc;
|
|
1887
1560
|
}, []);
|
|
1888
|
-
|
|
1889
|
-
if (!gen.operations) continue;
|
|
1890
|
-
const result = await gen.operations(pluginOperations, ctx);
|
|
1891
|
-
await this.dispatch({
|
|
1892
|
-
result,
|
|
1893
|
-
renderer: gen.renderer
|
|
1894
|
-
});
|
|
1895
|
-
}
|
|
1896
|
-
await this.hooks.emit("kubb:generate:operations", pluginOperations, ctx);
|
|
1561
|
+
await this.hooks.callHook("kubb:generate:operations", pluginOperations, ctx);
|
|
1897
1562
|
} catch (caughtError) {
|
|
1898
|
-
|
|
1899
|
-
state.error = caughtError;
|
|
1563
|
+
error = toError(caughtError);
|
|
1900
1564
|
}
|
|
1901
|
-
const duration = getElapsedMs(
|
|
1565
|
+
const duration = getElapsedMs(hrStart);
|
|
1902
1566
|
await this.#emitPluginEnd({
|
|
1903
|
-
plugin
|
|
1567
|
+
plugin,
|
|
1904
1568
|
duration,
|
|
1905
|
-
success: !
|
|
1906
|
-
error:
|
|
1569
|
+
success: !error,
|
|
1570
|
+
error: error ?? void 0
|
|
1907
1571
|
});
|
|
1908
|
-
if (
|
|
1909
|
-
...Diagnostics.from(
|
|
1910
|
-
plugin:
|
|
1572
|
+
if (error) diagnostics.push({
|
|
1573
|
+
...Diagnostics.from(error),
|
|
1574
|
+
plugin: plugin.name
|
|
1911
1575
|
});
|
|
1912
1576
|
diagnostics.push(Diagnostics.performance({
|
|
1913
|
-
plugin:
|
|
1577
|
+
plugin: plugin.name,
|
|
1914
1578
|
duration
|
|
1915
1579
|
}));
|
|
1916
1580
|
}
|
|
@@ -1938,10 +1602,6 @@ var KubbDriver = class {
|
|
|
1938
1602
|
}
|
|
1939
1603
|
if (!renderer) return;
|
|
1940
1604
|
const instance = _usingCtx$2.u(renderer());
|
|
1941
|
-
if (instance.stream) {
|
|
1942
|
-
for (const file of instance.stream(result)) this.fileManager.upsert(file);
|
|
1943
|
-
return;
|
|
1944
|
-
}
|
|
1945
1605
|
await instance.render(result);
|
|
1946
1606
|
this.fileManager.upsert(...instance.files);
|
|
1947
1607
|
} catch (_) {
|
|
@@ -1957,9 +1617,9 @@ var KubbDriver = class {
|
|
|
1957
1617
|
* @internal
|
|
1958
1618
|
*/
|
|
1959
1619
|
dispose() {
|
|
1960
|
-
for (const
|
|
1961
|
-
this.#
|
|
1962
|
-
this.#
|
|
1620
|
+
for (const unhook of this.#unhooks) unhook();
|
|
1621
|
+
this.#unhooks.length = 0;
|
|
1622
|
+
this.#hookGeneratorPlugins.clear();
|
|
1963
1623
|
this.#transforms.dispose();
|
|
1964
1624
|
this.#resolvers.clear();
|
|
1965
1625
|
this.#defaultResolvers.clear();
|
|
@@ -1970,26 +1630,21 @@ var KubbDriver = class {
|
|
|
1970
1630
|
[Symbol.dispose]() {
|
|
1971
1631
|
this.dispose();
|
|
1972
1632
|
}
|
|
1973
|
-
#getDefaultResolver = memoize(this.#defaultResolvers, (pluginName) =>
|
|
1974
|
-
name: "default",
|
|
1975
|
-
pluginName
|
|
1976
|
-
})));
|
|
1633
|
+
#getDefaultResolver = memoize(this.#defaultResolvers, (pluginName) => createResolver({ pluginName }));
|
|
1977
1634
|
/**
|
|
1978
1635
|
* Merges `partial` with the plugin's default resolver and stores the result.
|
|
1979
1636
|
* Also mirrors it onto `plugin.resolver` so callers using `getPlugin(name).resolver`
|
|
1980
1637
|
* get the up-to-date resolver without going through `getResolver()`.
|
|
1981
1638
|
*/
|
|
1982
1639
|
setPluginResolver(pluginName, partial) {
|
|
1983
|
-
const
|
|
1984
|
-
|
|
1985
|
-
...partial
|
|
1986
|
-
};
|
|
1640
|
+
const defaultResolver = this.#getDefaultResolver(pluginName);
|
|
1641
|
+
const merged = Resolver.merge(defaultResolver, partial);
|
|
1987
1642
|
this.#resolvers.set(pluginName, merged);
|
|
1988
1643
|
const plugin = this.plugins.get(pluginName);
|
|
1989
1644
|
if (plugin) plugin.resolver = merged;
|
|
1990
1645
|
}
|
|
1991
1646
|
getResolver(pluginName) {
|
|
1992
|
-
return this.#resolvers.get(pluginName) ?? this
|
|
1647
|
+
return this.#resolvers.get(pluginName) ?? this.#getDefaultResolver(pluginName);
|
|
1993
1648
|
}
|
|
1994
1649
|
getContext(plugin) {
|
|
1995
1650
|
const driver = this;
|
|
@@ -2057,18 +1712,18 @@ var KubbDriver = class {
|
|
|
2057
1712
|
return this.plugins.get(pluginName);
|
|
2058
1713
|
}
|
|
2059
1714
|
requirePlugin(pluginName, context) {
|
|
2060
|
-
const plugin = this.
|
|
2061
|
-
if (
|
|
2062
|
-
|
|
2063
|
-
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
}
|
|
2070
|
-
|
|
2071
|
-
|
|
1715
|
+
const plugin = this.getPlugin(pluginName);
|
|
1716
|
+
if (plugin) return plugin;
|
|
1717
|
+
const requiredBy = context?.requiredBy;
|
|
1718
|
+
const by = requiredBy ? ` by "${requiredBy}"` : "";
|
|
1719
|
+
const help = requiredBy ? ` (required by "${requiredBy}")` : "";
|
|
1720
|
+
throw new Diagnostics.Error({
|
|
1721
|
+
code: Diagnostics.code.pluginNotFound,
|
|
1722
|
+
severity: "error",
|
|
1723
|
+
message: `Plugin "${pluginName}" is required${by} but not found. Make sure it is included in your Kubb config.`,
|
|
1724
|
+
help: `Add "${pluginName}" to the \`plugins\` array in kubb.config.ts${help}, or remove the dependency on it.`,
|
|
1725
|
+
location: { kind: "config" }
|
|
1726
|
+
});
|
|
2072
1727
|
}
|
|
2073
1728
|
};
|
|
2074
1729
|
function inputToAdapterSource(config) {
|
|
@@ -2094,7 +1749,73 @@ function inputToAdapterSource(config) {
|
|
|
2094
1749
|
};
|
|
2095
1750
|
}
|
|
2096
1751
|
//#endregion
|
|
1752
|
+
//#region src/createStorage.ts
|
|
1753
|
+
/**
|
|
1754
|
+
* Defines a custom storage backend. The builder receives user options and
|
|
1755
|
+
* returns a `Storage` implementation. Kubb ships with filesystem and in-memory
|
|
1756
|
+
* storages. A custom backend writes generated files elsewhere, such as cloud
|
|
1757
|
+
* storage or a database.
|
|
1758
|
+
*
|
|
1759
|
+
* @example In-memory storage (the built-in implementation)
|
|
1760
|
+
* ```ts
|
|
1761
|
+
* import { createStorage } from '@kubb/core'
|
|
1762
|
+
*
|
|
1763
|
+
* export const memoryStorage = createStorage(() => {
|
|
1764
|
+
* const store = new Map<string, string>()
|
|
1765
|
+
*
|
|
1766
|
+
* return {
|
|
1767
|
+
* name: 'memory',
|
|
1768
|
+
* async hasItem(key) {
|
|
1769
|
+
* return store.has(key)
|
|
1770
|
+
* },
|
|
1771
|
+
* async getItem(key) {
|
|
1772
|
+
* return store.get(key) ?? null
|
|
1773
|
+
* },
|
|
1774
|
+
* async setItem(key, value) {
|
|
1775
|
+
* store.set(key, value)
|
|
1776
|
+
* },
|
|
1777
|
+
* async removeItem(key) {
|
|
1778
|
+
* store.delete(key)
|
|
1779
|
+
* },
|
|
1780
|
+
* async getKeys(base) {
|
|
1781
|
+
* const keys = [...store.keys()]
|
|
1782
|
+
* return base ? keys.filter((k) => k.startsWith(base)) : keys
|
|
1783
|
+
* },
|
|
1784
|
+
* async clear(base) {
|
|
1785
|
+
* if (!base) store.clear()
|
|
1786
|
+
* },
|
|
1787
|
+
* }
|
|
1788
|
+
* })
|
|
1789
|
+
* ```
|
|
1790
|
+
*/
|
|
1791
|
+
function createStorage(build) {
|
|
1792
|
+
return (options) => build(options ?? {});
|
|
1793
|
+
}
|
|
1794
|
+
//#endregion
|
|
2097
1795
|
//#region src/storages/fsStorage.ts
|
|
1796
|
+
const WRITE_CONCURRENCY = 50;
|
|
1797
|
+
function createLimiter(concurrency) {
|
|
1798
|
+
let active = 0;
|
|
1799
|
+
const queue = [];
|
|
1800
|
+
function next() {
|
|
1801
|
+
if (active >= concurrency) return;
|
|
1802
|
+
const run = queue.shift();
|
|
1803
|
+
if (!run) return;
|
|
1804
|
+
active++;
|
|
1805
|
+
run();
|
|
1806
|
+
}
|
|
1807
|
+
return function limit(task) {
|
|
1808
|
+
return new Promise((resolve, reject) => {
|
|
1809
|
+
queue.push(() => {
|
|
1810
|
+
task().then(resolve, reject).finally(() => {
|
|
1811
|
+
active--;
|
|
1812
|
+
next();
|
|
1813
|
+
});
|
|
1814
|
+
});
|
|
1815
|
+
next();
|
|
1816
|
+
});
|
|
1817
|
+
};
|
|
1818
|
+
}
|
|
2098
1819
|
/**
|
|
2099
1820
|
* Built-in filesystem storage driver.
|
|
2100
1821
|
*
|
|
@@ -2107,6 +1828,8 @@ function inputToAdapterSource(config) {
|
|
|
2107
1828
|
* - the write is skipped when the file content is already identical
|
|
2108
1829
|
* - missing parent directories are created automatically
|
|
2109
1830
|
* - Bun's native file API is used when running under Bun
|
|
1831
|
+
* - concurrent `setItem` calls are capped at {@link WRITE_CONCURRENCY} in flight, so a caller
|
|
1832
|
+
* can fire every file's write without pacing itself
|
|
2110
1833
|
*
|
|
2111
1834
|
* @example
|
|
2112
1835
|
* ```ts
|
|
@@ -2120,91 +1843,50 @@ function inputToAdapterSource(config) {
|
|
|
2120
1843
|
* })
|
|
2121
1844
|
* ```
|
|
2122
1845
|
*/
|
|
2123
|
-
const fsStorage = createStorage(() =>
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
await access(resolve(key));
|
|
2128
|
-
return true;
|
|
2129
|
-
} catch (_error) {
|
|
2130
|
-
return false;
|
|
2131
|
-
}
|
|
2132
|
-
},
|
|
2133
|
-
async getItem(key) {
|
|
2134
|
-
try {
|
|
2135
|
-
return await readFile(resolve(key), "utf8");
|
|
2136
|
-
} catch (_error) {
|
|
2137
|
-
return null;
|
|
2138
|
-
}
|
|
2139
|
-
},
|
|
2140
|
-
async setItem(key, value) {
|
|
2141
|
-
await write(resolve(key), value, { sanity: false });
|
|
2142
|
-
},
|
|
2143
|
-
async removeItem(key) {
|
|
2144
|
-
await rm(resolve(key), { force: true });
|
|
2145
|
-
},
|
|
2146
|
-
async getKeys(base) {
|
|
2147
|
-
const resolvedBase = resolve(base ?? process.cwd());
|
|
2148
|
-
if (runtime.isBun) {
|
|
2149
|
-
const bunGlob = new Bun.Glob("**/*");
|
|
2150
|
-
return Array.fromAsync(bunGlob.scan({
|
|
2151
|
-
cwd: resolvedBase,
|
|
2152
|
-
onlyFiles: true,
|
|
2153
|
-
dot: true
|
|
2154
|
-
}));
|
|
2155
|
-
}
|
|
2156
|
-
const keys = [];
|
|
2157
|
-
try {
|
|
2158
|
-
for await (const entry of glob("**/*", {
|
|
2159
|
-
cwd: resolvedBase,
|
|
2160
|
-
withFileTypes: true
|
|
2161
|
-
})) if (entry.isFile()) keys.push(toPosixPath(relative(resolvedBase, join(entry.parentPath, entry.name))));
|
|
2162
|
-
} catch (_error) {}
|
|
2163
|
-
return keys;
|
|
2164
|
-
},
|
|
2165
|
-
async clear(base) {
|
|
2166
|
-
if (!base) return;
|
|
2167
|
-
await clean(resolve(base));
|
|
2168
|
-
}
|
|
2169
|
-
}));
|
|
2170
|
-
//#endregion
|
|
2171
|
-
//#region src/createKubb.ts
|
|
2172
|
-
/**
|
|
2173
|
-
* Builds a `Storage` view scoped to the file paths produced by the current build.
|
|
2174
|
-
* Reads delegate to the underlying `storage` so source bytes stay where they were
|
|
2175
|
-
* written. Writes register the key so subsequent reads and `getKeys` are scoped
|
|
2176
|
-
* to this build's output.
|
|
2177
|
-
*/
|
|
2178
|
-
function createSourcesView(storage) {
|
|
2179
|
-
const paths = /* @__PURE__ */ new Set();
|
|
2180
|
-
return createStorage(() => ({
|
|
2181
|
-
name: `${storage.name}:sources`,
|
|
1846
|
+
const fsStorage = createStorage(() => {
|
|
1847
|
+
const limit = createLimiter(WRITE_CONCURRENCY);
|
|
1848
|
+
return {
|
|
1849
|
+
name: "fs",
|
|
2182
1850
|
async hasItem(key) {
|
|
2183
|
-
|
|
1851
|
+
try {
|
|
1852
|
+
await access(resolve(key));
|
|
1853
|
+
return true;
|
|
1854
|
+
} catch (_error) {
|
|
1855
|
+
return false;
|
|
1856
|
+
}
|
|
2184
1857
|
},
|
|
2185
1858
|
async getItem(key) {
|
|
2186
|
-
|
|
1859
|
+
try {
|
|
1860
|
+
return await readFile(resolve(key), "utf8");
|
|
1861
|
+
} catch (_error) {
|
|
1862
|
+
return null;
|
|
1863
|
+
}
|
|
2187
1864
|
},
|
|
2188
1865
|
async setItem(key, value) {
|
|
2189
|
-
|
|
2190
|
-
await storage.setItem(key, value);
|
|
1866
|
+
await limit(() => write(resolve(key), value, { sanity: false }));
|
|
2191
1867
|
},
|
|
2192
1868
|
async removeItem(key) {
|
|
2193
|
-
|
|
2194
|
-
await storage.removeItem(key);
|
|
1869
|
+
await rm(resolve(key), { force: true });
|
|
2195
1870
|
},
|
|
2196
1871
|
async getKeys(base) {
|
|
2197
|
-
|
|
2198
|
-
const
|
|
2199
|
-
|
|
2200
|
-
|
|
1872
|
+
const resolvedBase = resolve(base ?? process.cwd());
|
|
1873
|
+
const keys = [];
|
|
1874
|
+
try {
|
|
1875
|
+
for await (const entry of glob("**/*", {
|
|
1876
|
+
cwd: resolvedBase,
|
|
1877
|
+
withFileTypes: true
|
|
1878
|
+
})) if (entry.isFile()) keys.push(toPosixPath(relative(resolvedBase, join(entry.parentPath, entry.name))));
|
|
1879
|
+
} catch (_error) {}
|
|
1880
|
+
return keys;
|
|
2201
1881
|
},
|
|
2202
|
-
async clear() {
|
|
2203
|
-
|
|
2204
|
-
await
|
|
1882
|
+
async clear(base) {
|
|
1883
|
+
if (!base) return;
|
|
1884
|
+
await clean(resolve(base));
|
|
2205
1885
|
}
|
|
2206
|
-
}
|
|
2207
|
-
}
|
|
1886
|
+
};
|
|
1887
|
+
});
|
|
1888
|
+
//#endregion
|
|
1889
|
+
//#region src/createKubb.ts
|
|
2208
1890
|
function resolveConfig(userConfig) {
|
|
2209
1891
|
return {
|
|
2210
1892
|
...userConfig,
|
|
@@ -2230,12 +1912,12 @@ function resolveConfig(userConfig) {
|
|
|
2230
1912
|
* `createKubb` takes a plain config object (the shape `defineConfig` produces),
|
|
2231
1913
|
* not a fluent builder.
|
|
2232
1914
|
*
|
|
2233
|
-
* Attach
|
|
1915
|
+
* Attach hook listeners to `.hooks` before calling `setup()` or `build()`.
|
|
2234
1916
|
*
|
|
2235
1917
|
* @example
|
|
2236
1918
|
* ```ts
|
|
2237
1919
|
* const kubb = createKubb(userConfig)
|
|
2238
|
-
* kubb.hooks.
|
|
1920
|
+
* kubb.hooks.hook('kubb:plugin:end', ({ plugin, duration }) => console.log(plugin.name, duration))
|
|
2239
1921
|
* const { files, diagnostics } = await kubb.safeBuild()
|
|
2240
1922
|
* ```
|
|
2241
1923
|
*/
|
|
@@ -2246,7 +1928,7 @@ var Kubb = class {
|
|
|
2246
1928
|
#storage = null;
|
|
2247
1929
|
constructor(userConfig, options = {}) {
|
|
2248
1930
|
this.config = resolveConfig(userConfig);
|
|
2249
|
-
this.hooks = options.hooks ?? new
|
|
1931
|
+
this.hooks = options.hooks ?? new Hookable();
|
|
2250
1932
|
}
|
|
2251
1933
|
get storage() {
|
|
2252
1934
|
if (!this.#storage) throw new Error("[kubb] setup() must be called before accessing storage");
|
|
@@ -2262,12 +1944,11 @@ var Kubb = class {
|
|
|
2262
1944
|
async setup() {
|
|
2263
1945
|
const config = this.config;
|
|
2264
1946
|
const driver = new KubbDriver(config, { hooks: this.hooks });
|
|
2265
|
-
const storage = createSourcesView(config.storage);
|
|
2266
1947
|
this.hooks.setMaxListeners(Math.max(10, config.plugins.length * 4));
|
|
2267
1948
|
if (config.output.clean) await config.storage.clear(resolve(config.root, config.output.path));
|
|
2268
1949
|
await driver.setup();
|
|
2269
1950
|
this.#driver = driver;
|
|
2270
|
-
this.#storage = storage;
|
|
1951
|
+
this.#storage = config.storage;
|
|
2271
1952
|
}
|
|
2272
1953
|
/**
|
|
2273
1954
|
* Runs the full pipeline and throws on any plugin error.
|
|
@@ -2290,9 +1971,9 @@ var Kubb = class {
|
|
|
2290
1971
|
try {
|
|
2291
1972
|
var _usingCtx$1 = _usingCtx();
|
|
2292
1973
|
if (!this.#driver) await this.setup();
|
|
2293
|
-
const
|
|
2294
|
-
const driver =
|
|
2295
|
-
const storage =
|
|
1974
|
+
const self = _usingCtx$1.u(this);
|
|
1975
|
+
const driver = self.driver;
|
|
1976
|
+
const storage = self.storage;
|
|
2296
1977
|
const { diagnostics } = await driver.run({ storage });
|
|
2297
1978
|
return {
|
|
2298
1979
|
diagnostics,
|
|
@@ -2353,7 +2034,7 @@ const logLevel = {
|
|
|
2353
2034
|
/**
|
|
2354
2035
|
* Defines a reporter. When the definition has a `drain`, the returned reporter buffers each value
|
|
2355
2036
|
* `report` returns and hands the array to `drain` once, then clears it. Without a `drain`, nothing
|
|
2356
|
-
* is buffered. Wiring the reporter onto the run's
|
|
2037
|
+
* is buffered. Wiring the reporter onto the run's hooks is the host's job, so the reporter only
|
|
2357
2038
|
* ever deals with a {@link GenerationResult}.
|
|
2358
2039
|
*
|
|
2359
2040
|
* @example
|
|
@@ -2372,22 +2053,19 @@ const logLevel = {
|
|
|
2372
2053
|
* ```
|
|
2373
2054
|
*/
|
|
2374
2055
|
function createReporter(reporter) {
|
|
2375
|
-
const
|
|
2376
|
-
if (!drain) return {
|
|
2377
|
-
name: reporter.name,
|
|
2378
|
-
async report(result, context) {
|
|
2379
|
-
await reporter.report(result, context);
|
|
2380
|
-
}
|
|
2381
|
-
};
|
|
2382
|
-
const reports = [];
|
|
2056
|
+
const reports = /* @__PURE__ */ new Set();
|
|
2383
2057
|
return {
|
|
2384
2058
|
name: reporter.name,
|
|
2385
2059
|
async report(result, context) {
|
|
2386
|
-
|
|
2060
|
+
const report = await reporter.report(result, context);
|
|
2061
|
+
reports.add(report);
|
|
2387
2062
|
},
|
|
2388
2063
|
async drain(context) {
|
|
2389
|
-
await drain(context, reports);
|
|
2390
|
-
reports.
|
|
2064
|
+
await reporter.drain?.(context, Array.from(reports));
|
|
2065
|
+
reports.clear();
|
|
2066
|
+
},
|
|
2067
|
+
[Symbol.dispose]() {
|
|
2068
|
+
reports.clear();
|
|
2391
2069
|
}
|
|
2392
2070
|
};
|
|
2393
2071
|
}
|
|
@@ -2540,7 +2218,7 @@ function buildTimingSection(report) {
|
|
|
2540
2218
|
* `## Timings` section. Selected with `--reporter file` (or `reporters: ['file']`).
|
|
2541
2219
|
*
|
|
2542
2220
|
* @note It captures the collected diagnostics once a config finishes, not the live
|
|
2543
|
-
* `kubb:info`/`kubb:plugin`
|
|
2221
|
+
* `kubb:info`/`kubb:plugin` hook stream. Color is stripped so the file stays plain text even when
|
|
2544
2222
|
* the run is attached to a TTY.
|
|
2545
2223
|
*/
|
|
2546
2224
|
const fileReporter = createReporter({
|
|
@@ -2678,6 +2356,56 @@ function defineParser(parser) {
|
|
|
2678
2356
|
return parser;
|
|
2679
2357
|
}
|
|
2680
2358
|
//#endregion
|
|
2681
|
-
|
|
2359
|
+
//#region src/storages/memoryStorage.ts
|
|
2360
|
+
/**
|
|
2361
|
+
* In-memory storage driver. Useful for testing and dry-run scenarios where
|
|
2362
|
+
* generated output should be captured without touching the filesystem.
|
|
2363
|
+
*
|
|
2364
|
+
* All data lives in a `Map` scoped to the storage instance and is discarded
|
|
2365
|
+
* when the instance is garbage-collected.
|
|
2366
|
+
*
|
|
2367
|
+
* @example
|
|
2368
|
+
* ```ts
|
|
2369
|
+
* import { memoryStorage } from '@kubb/core'
|
|
2370
|
+
* import { defineConfig } from 'kubb'
|
|
2371
|
+
*
|
|
2372
|
+
* export default defineConfig({
|
|
2373
|
+
* input: { path: './petStore.yaml' },
|
|
2374
|
+
* output: { path: './src/gen' },
|
|
2375
|
+
* storage: memoryStorage(),
|
|
2376
|
+
* })
|
|
2377
|
+
* ```
|
|
2378
|
+
*/
|
|
2379
|
+
const memoryStorage = createStorage(() => {
|
|
2380
|
+
const store = /* @__PURE__ */ new Map();
|
|
2381
|
+
return {
|
|
2382
|
+
name: "memory",
|
|
2383
|
+
async hasItem(key) {
|
|
2384
|
+
return store.has(key);
|
|
2385
|
+
},
|
|
2386
|
+
async getItem(key) {
|
|
2387
|
+
return store.get(key) ?? null;
|
|
2388
|
+
},
|
|
2389
|
+
async setItem(key, value) {
|
|
2390
|
+
store.set(key, value);
|
|
2391
|
+
},
|
|
2392
|
+
async removeItem(key) {
|
|
2393
|
+
store.delete(key);
|
|
2394
|
+
},
|
|
2395
|
+
async getKeys(base) {
|
|
2396
|
+
const keys = [...store.keys()];
|
|
2397
|
+
return base ? keys.filter((k) => k.startsWith(base)) : keys;
|
|
2398
|
+
},
|
|
2399
|
+
async clear(base) {
|
|
2400
|
+
if (!base) {
|
|
2401
|
+
store.clear();
|
|
2402
|
+
return;
|
|
2403
|
+
}
|
|
2404
|
+
for (const key of store.keys()) if (key.startsWith(base)) store.delete(key);
|
|
2405
|
+
}
|
|
2406
|
+
};
|
|
2407
|
+
});
|
|
2408
|
+
//#endregion
|
|
2409
|
+
export { Diagnostics, Hookable, KubbDriver, Resolver, cliReporter, createAdapter, createKubb, createRenderer, createReporter, createResolver, createStorage, defineGenerator, defineParser, definePlugin, fileReporter, fsStorage, jsonReporter, logLevel, memoryStorage };
|
|
2682
2410
|
|
|
2683
2411
|
//# sourceMappingURL=index.js.map
|