@kubb/core 5.0.0-beta.82 → 5.0.0-beta.85
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/{diagnostics-DuaXn2bT.d.ts → Diagnostics-PnPLU3kj.d.ts} +314 -402
- package/dist/index.cjs +729 -973
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +4 -93
- package/dist/index.js +690 -934
- 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-BjUIqpYE.js → usingCtx-BABTfo1g.js} +160 -480
- package/dist/usingCtx-BABTfo1g.js.map +1 -0
- package/dist/{memoryStorage-DQ6qXJ8o.cjs → usingCtx-fxRpJw0P.cjs} +159 -521
- package/dist/usingCtx-fxRpJw0P.cjs.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, i as clean, l as getErrorMessage, n as FileManager, o as toPosixPath, r as AsyncEventEmitter, s as write, t as _usingCtx, u as camelCase } from "./usingCtx-BABTfo1g.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.85";
|
|
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();
|
|
@@ -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,284 @@ 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/createResolver.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`.
|
|
748
|
+
* Base constraint for all plugin resolver objects.
|
|
913
749
|
*
|
|
914
|
-
* -
|
|
915
|
-
*
|
|
916
|
-
*
|
|
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.
|
|
925
|
-
*
|
|
926
|
-
* Returns `null` when the node is filtered out by an `exclude` rule or not matched by any `include` rule.
|
|
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 createResolver({
|
|
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
|
+
};
|
|
1213
999
|
/**
|
|
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')`.
|
|
1000
|
+
* Defines a plugin resolver, the object that decides what every generated symbol and file
|
|
1001
|
+
* path is called. Override the top-level `name` and `file` to set the plugin's conventions,
|
|
1002
|
+
* and add your own naming helpers, top-level (`typeName`, …) or grouped in namespaces
|
|
1003
|
+
* (`query`, `schema`, …). Every method reaches sibling helpers and the built-in machinery
|
|
1004
|
+
* through `this.name`, `this.file`, and `this.default`.
|
|
1227
1005
|
*
|
|
1228
|
-
* @example
|
|
1006
|
+
* @example Custom identifier and file casing
|
|
1229
1007
|
* ```ts
|
|
1230
|
-
* export const resolverTs =
|
|
1231
|
-
*
|
|
1232
|
-
*
|
|
1233
|
-
* return
|
|
1008
|
+
* export const resolverTs = createResolver<PluginTs>({
|
|
1009
|
+
* pluginName: 'plugin-ts',
|
|
1010
|
+
* name(name) {
|
|
1011
|
+
* return ensureValidVarName(pascalCase(name))
|
|
1234
1012
|
* },
|
|
1235
|
-
*
|
|
1236
|
-
* return this.default(name,
|
|
1013
|
+
* file(params, context) {
|
|
1014
|
+
* return this.default.file({ ...params, resolveName: (name) => toFilePath(name, pascalCase) }, context)
|
|
1237
1015
|
* },
|
|
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
|
-
* }))
|
|
1016
|
+
* })
|
|
1251
1017
|
* ```
|
|
1252
1018
|
*/
|
|
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;
|
|
1019
|
+
function createResolver(options) {
|
|
1020
|
+
return new Resolver(options);
|
|
1265
1021
|
}
|
|
1266
1022
|
//#endregion
|
|
1267
1023
|
//#region src/Transform.ts
|
|
@@ -1282,12 +1038,6 @@ var Transform = class {
|
|
|
1282
1038
|
#composed = /* @__PURE__ */ new Map();
|
|
1283
1039
|
#memo = /* @__PURE__ */ new Map();
|
|
1284
1040
|
/**
|
|
1285
|
-
* Number of plugins with at least one registered macro.
|
|
1286
|
-
*/
|
|
1287
|
-
get size() {
|
|
1288
|
-
return this.#macros.size;
|
|
1289
|
-
}
|
|
1290
|
-
/**
|
|
1291
1041
|
* Appends `macro` to the plugin's list, after any macros already registered.
|
|
1292
1042
|
*/
|
|
1293
1043
|
add(pluginName, macro) {
|
|
@@ -1304,12 +1054,6 @@ var Transform = class {
|
|
|
1304
1054
|
this.#invalidate(pluginName);
|
|
1305
1055
|
}
|
|
1306
1056
|
/**
|
|
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
1057
|
* Runs the plugin's macros on `node`. Returns the original node reference when the plugin has no
|
|
1314
1058
|
* macros, so callers can compare by identity to detect a no-op.
|
|
1315
1059
|
*/
|
|
@@ -1353,20 +1097,49 @@ var Transform = class {
|
|
|
1353
1097
|
};
|
|
1354
1098
|
//#endregion
|
|
1355
1099
|
//#region src/KubbDriver.ts
|
|
1356
|
-
|
|
1357
|
-
|
|
1100
|
+
const ENFORCE_ORDER = {
|
|
1101
|
+
pre: -1,
|
|
1102
|
+
post: 1
|
|
1103
|
+
};
|
|
1104
|
+
/**
|
|
1105
|
+
* Orders plugins so every dependency runs before its dependents (Kahn's algorithm), with
|
|
1106
|
+
* `enforce` (`'pre'` before normal before `'post'`) and declaration order as tiebreaks.
|
|
1107
|
+
* A pairwise `Array.sort` comparator cannot do this: dependency relations are not transitive
|
|
1108
|
+
* at the comparator level, so a chain where A depends on B and B depends on C could come out
|
|
1109
|
+
* wrong when A and C are never compared directly. Dependencies on plugins missing from the
|
|
1110
|
+
* config are ignored here and surface later through `requirePlugin`.
|
|
1111
|
+
*/
|
|
1112
|
+
function sortPlugins(plugins) {
|
|
1113
|
+
const queue = [...plugins].sort((a, b) => (a.enforce ? ENFORCE_ORDER[a.enforce] : 0) - (b.enforce ? ENFORCE_ORDER[b.enforce] : 0));
|
|
1114
|
+
const names = new Set(queue.map((plugin) => plugin.name));
|
|
1115
|
+
const blockedBy = new Map(queue.map((plugin) => [plugin.name, new Set(plugin.dependencies?.filter((name) => names.has(name) && name !== plugin.name))]));
|
|
1116
|
+
const sorted = [];
|
|
1117
|
+
while (queue.length > 0) {
|
|
1118
|
+
const index = queue.findIndex((plugin) => blockedBy.get(plugin.name)?.size === 0);
|
|
1119
|
+
if (index === -1) throw new Diagnostics.Error({
|
|
1120
|
+
code: Diagnostics.code.invalidPluginOptions,
|
|
1121
|
+
severity: "error",
|
|
1122
|
+
message: `Plugin dependencies form a cycle: ${queue.map((plugin) => plugin.name).join(" → ")}.`,
|
|
1123
|
+
help: "Remove one of the `dependencies` entries so the plugins can be ordered.",
|
|
1124
|
+
location: { kind: "config" }
|
|
1125
|
+
});
|
|
1126
|
+
const [plugin] = queue.splice(index, 1);
|
|
1127
|
+
if (!plugin) break;
|
|
1128
|
+
sorted.push(plugin);
|
|
1129
|
+
for (const blockers of blockedBy.values()) blockers.delete(plugin.name);
|
|
1130
|
+
}
|
|
1131
|
+
return sorted;
|
|
1358
1132
|
}
|
|
1359
1133
|
var KubbDriver = class {
|
|
1360
1134
|
config;
|
|
1361
1135
|
options;
|
|
1362
1136
|
/**
|
|
1363
|
-
* The
|
|
1364
|
-
* Parse-only adapters are wrapped automatically.
|
|
1137
|
+
* The `InputNode` produced by the adapter. Set after adapter setup.
|
|
1365
1138
|
*/
|
|
1366
1139
|
inputNode = null;
|
|
1367
1140
|
adapter = null;
|
|
1368
1141
|
/**
|
|
1369
|
-
* Raw adapter source so `adapter.parse()`
|
|
1142
|
+
* Raw adapter source so `adapter.parse()` can run lazily.
|
|
1370
1143
|
* Intentionally outlives the build, cleared by `dispose()`.
|
|
1371
1144
|
*/
|
|
1372
1145
|
#adapterSource = null;
|
|
@@ -1414,15 +1187,8 @@ var KubbDriver = class {
|
|
|
1414
1187
|
* so `run` can parse it later.
|
|
1415
1188
|
*/
|
|
1416
1189
|
async setup() {
|
|
1417
|
-
const normalized = this.config.plugins.map((rawPlugin) => this.#normalizePlugin(rawPlugin));
|
|
1418
|
-
const dependenciesByName = new Map(normalized.map((plugin) => [plugin.name, new Set(plugin.dependencies ?? [])]));
|
|
1419
|
-
normalized.sort((a, b) => {
|
|
1420
|
-
if (dependenciesByName.get(b.name)?.has(a.name)) return -1;
|
|
1421
|
-
if (dependenciesByName.get(a.name)?.has(b.name)) return 1;
|
|
1422
|
-
return enforceOrder(a.enforce) - enforceOrder(b.enforce);
|
|
1423
|
-
});
|
|
1190
|
+
const normalized = sortPlugins(this.config.plugins.map((rawPlugin) => this.#normalizePlugin(rawPlugin)));
|
|
1424
1191
|
for (const plugin of normalized) {
|
|
1425
|
-
if (plugin.apply) plugin.apply(this.config);
|
|
1426
1192
|
this.#registerPlugin(plugin);
|
|
1427
1193
|
this.plugins.set(plugin.name, plugin);
|
|
1428
1194
|
}
|
|
@@ -1433,11 +1199,11 @@ var KubbDriver = class {
|
|
|
1433
1199
|
}
|
|
1434
1200
|
/**
|
|
1435
1201
|
* Builds a `NormalizedPlugin` from a hook-style plugin, filling in default
|
|
1436
|
-
* options
|
|
1437
|
-
*
|
|
1202
|
+
* options. Registering its lifecycle handlers on the `AsyncEventEmitter` is
|
|
1203
|
+
* done separately by `#registerPlugin`.
|
|
1438
1204
|
*/
|
|
1439
1205
|
#normalizePlugin(plugin) {
|
|
1440
|
-
|
|
1206
|
+
return {
|
|
1441
1207
|
name: plugin.name,
|
|
1442
1208
|
dependencies: plugin.dependencies,
|
|
1443
1209
|
enforce: plugin.enforce,
|
|
@@ -1451,30 +1217,14 @@ var KubbDriver = class {
|
|
|
1451
1217
|
override: []
|
|
1452
1218
|
}
|
|
1453
1219
|
};
|
|
1454
|
-
if ("apply" in plugin && typeof plugin.apply === "function") normalized.apply = plugin.apply;
|
|
1455
|
-
return normalized;
|
|
1456
1220
|
}
|
|
1457
1221
|
/**
|
|
1458
1222
|
* 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.
|
|
1223
|
+
* `run` do not re-parse.
|
|
1462
1224
|
*/
|
|
1463
1225
|
async #parseInput() {
|
|
1464
1226
|
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
|
-
});
|
|
1227
|
+
this.inputNode = await this.adapter.parse(this.#adapterSource);
|
|
1478
1228
|
}
|
|
1479
1229
|
/**
|
|
1480
1230
|
* Registers a hook-style plugin's lifecycle handlers on the shared `AsyncEventEmitter`.
|
|
@@ -1588,10 +1338,10 @@ var KubbDriver = class {
|
|
|
1588
1338
|
}
|
|
1589
1339
|
/**
|
|
1590
1340
|
* Returns `true` when at least one generator was registered for the given plugin
|
|
1591
|
-
* via `addGenerator()` in `kubb:plugin:setup
|
|
1341
|
+
* via `addGenerator()` in `kubb:plugin:setup`.
|
|
1592
1342
|
*
|
|
1593
1343
|
* Used by the build loop to decide whether to walk the AST and emit generator events
|
|
1594
|
-
* for a plugin
|
|
1344
|
+
* for a plugin.
|
|
1595
1345
|
*/
|
|
1596
1346
|
hasEventGenerators(pluginName) {
|
|
1597
1347
|
return this.#eventGeneratorPlugins.has(pluginName);
|
|
@@ -1603,34 +1353,28 @@ var KubbDriver = class {
|
|
|
1603
1353
|
* contributes a `timing` diagnostic for the run summary.
|
|
1604
1354
|
*/
|
|
1605
1355
|
async run({ storage }) {
|
|
1606
|
-
const { hooks, config } = this;
|
|
1356
|
+
const { hooks, config, fileManager } = this;
|
|
1607
1357
|
const diagnostics = [];
|
|
1608
1358
|
const parsersMap = /* @__PURE__ */ new Map();
|
|
1609
1359
|
for (const parser of config.parsers) if (parser.extNames) for (const ext of parser.extNames) parsersMap.set(ext, parser);
|
|
1610
|
-
const
|
|
1611
|
-
parsers: parsersMap,
|
|
1612
|
-
storage,
|
|
1613
|
-
extension: config.output.extension
|
|
1614
|
-
});
|
|
1615
|
-
processor.hooks.on("start", async (files) => {
|
|
1360
|
+
const onWriteStart = async (files) => {
|
|
1616
1361
|
await hooks.emit("kubb:files:processing:start", { files });
|
|
1617
|
-
}
|
|
1362
|
+
};
|
|
1618
1363
|
const updateBuffer = [];
|
|
1619
|
-
|
|
1364
|
+
const onWriteUpdate = (item) => {
|
|
1620
1365
|
updateBuffer.push(item);
|
|
1621
|
-
}
|
|
1622
|
-
|
|
1366
|
+
};
|
|
1367
|
+
const onWriteEnd = async (files) => {
|
|
1623
1368
|
await hooks.emit("kubb:files:processing:update", { files: updateBuffer.map((item) => ({
|
|
1624
1369
|
...item,
|
|
1625
1370
|
config
|
|
1626
1371
|
})) });
|
|
1627
1372
|
updateBuffer.length = 0;
|
|
1628
1373
|
await hooks.emit("kubb:files:processing:end", { files });
|
|
1629
|
-
});
|
|
1630
|
-
const onFileUpsert = (file) => {
|
|
1631
|
-
processor.enqueue(file);
|
|
1632
1374
|
};
|
|
1633
|
-
|
|
1375
|
+
fileManager.hooks.on("start", onWriteStart);
|
|
1376
|
+
fileManager.hooks.on("update", onWriteUpdate);
|
|
1377
|
+
fileManager.hooks.on("end", onWriteEnd);
|
|
1634
1378
|
return Diagnostics.scope((diagnostic) => diagnostics.push(diagnostic), async () => {
|
|
1635
1379
|
try {
|
|
1636
1380
|
const outputRoot = resolve(config.root, config.output.path);
|
|
@@ -1685,10 +1429,13 @@ var KubbDriver = class {
|
|
|
1685
1429
|
success: true
|
|
1686
1430
|
});
|
|
1687
1431
|
}
|
|
1688
|
-
diagnostics.push(...await this.#runGenerators(generatorPlugins
|
|
1689
|
-
await processor.drain();
|
|
1432
|
+
diagnostics.push(...await this.#runGenerators(generatorPlugins));
|
|
1690
1433
|
await hooks.emit("kubb:plugins:end", Object.assign({ config }, this.#filesPayload()));
|
|
1691
|
-
await
|
|
1434
|
+
await fileManager.write(fileManager.files, {
|
|
1435
|
+
storage,
|
|
1436
|
+
parsers: parsersMap,
|
|
1437
|
+
extension: config.output.extension
|
|
1438
|
+
});
|
|
1692
1439
|
await hooks.emit("kubb:build:end", {
|
|
1693
1440
|
files: this.fileManager.files,
|
|
1694
1441
|
config,
|
|
@@ -1699,7 +1446,9 @@ var KubbDriver = class {
|
|
|
1699
1446
|
diagnostics.push(Diagnostics.from(caughtError));
|
|
1700
1447
|
return { diagnostics: Diagnostics.dedupe(diagnostics) };
|
|
1701
1448
|
} finally {
|
|
1702
|
-
|
|
1449
|
+
fileManager.hooks.off("start", onWriteStart);
|
|
1450
|
+
fileManager.hooks.off("update", onWriteUpdate);
|
|
1451
|
+
fileManager.hooks.off("end", onWriteEnd);
|
|
1703
1452
|
}
|
|
1704
1453
|
});
|
|
1705
1454
|
}
|
|
@@ -1722,22 +1471,21 @@ var KubbDriver = class {
|
|
|
1722
1471
|
}, this.#filesPayload()));
|
|
1723
1472
|
}
|
|
1724
1473
|
/**
|
|
1725
|
-
*
|
|
1474
|
+
* Runs schemas and operations through every plugin's generators. Each node is run
|
|
1726
1475
|
* through the plugin's macros (from `this.#transforms`) before the generator sees it,
|
|
1727
1476
|
* so plugins stay isolated and the hot path stays per-node. Schemas run before operations
|
|
1728
|
-
*
|
|
1477
|
+
* so file output stays deterministic across runs.
|
|
1729
1478
|
* A failing plugin contributes an error diagnostic so the rest of the build continues.
|
|
1730
1479
|
* Every plugin also contributes a `timing` diagnostic.
|
|
1731
1480
|
*
|
|
1732
|
-
* Plugins
|
|
1733
|
-
*
|
|
1734
|
-
*
|
|
1735
|
-
* sit at the initial value until the very end of the run.
|
|
1481
|
+
* Plugins are processed one at a time, in full, so `kubb:plugin:end` fires as each one
|
|
1482
|
+
* completes rather than all at once at the end. That ordering drives the CLI's
|
|
1483
|
+
* `Plugins N/M` counter.
|
|
1736
1484
|
*
|
|
1737
1485
|
* When `this.inputNode` is `null`, every entry still gets a `kubb:plugin:end` so
|
|
1738
1486
|
* post-plugin listeners (the barrel writer and friends) complete.
|
|
1739
1487
|
*/
|
|
1740
|
-
async #runGenerators(entries
|
|
1488
|
+
async #runGenerators(entries) {
|
|
1741
1489
|
const diagnostics = [];
|
|
1742
1490
|
if (entries.length === 0) return diagnostics;
|
|
1743
1491
|
if (!this.inputNode) {
|
|
@@ -1757,160 +1505,104 @@ var KubbDriver = class {
|
|
|
1757
1505
|
}
|
|
1758
1506
|
const transforms = this.#transforms;
|
|
1759
1507
|
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
1508
|
const emitsSchemaHook = this.hooks.listenerCount("kubb:generate:schema") > 0;
|
|
1780
1509
|
const emitsOperationHook = this.hooks.listenerCount("kubb:generate:operation") > 0;
|
|
1781
1510
|
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
|
-
};
|
|
1511
|
+
const allowedSchemaNamesByPlugin = /* @__PURE__ */ new Map();
|
|
1512
|
+
for (const { plugin } of entries) {
|
|
1811
1513
|
const { exclude, include, override } = plugin.options;
|
|
1812
|
-
|
|
1514
|
+
if (!((include?.some(({ type }) => OPERATION_FILTER_TYPES.has(type)) ?? false) && !(include?.some(({ type }) => type === "schemaName") ?? false))) continue;
|
|
1515
|
+
const resolver = this.getResolver(plugin.name);
|
|
1516
|
+
const includedOps = operations.filter((operation) => resolver.default.options(operation, {
|
|
1813
1517
|
options: plugin.options,
|
|
1814
1518
|
exclude,
|
|
1815
1519
|
include,
|
|
1816
1520
|
override
|
|
1817
|
-
});
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
1521
|
+
}) !== null);
|
|
1522
|
+
allowedSchemaNamesByPlugin.set(plugin.name, collectUsedSchemaNames(includedOps, schemas));
|
|
1523
|
+
}
|
|
1524
|
+
for (const { plugin, context, hrStart } of entries) {
|
|
1525
|
+
const generatorContext = {
|
|
1526
|
+
...context,
|
|
1527
|
+
resolver: this.getResolver(plugin.name)
|
|
1822
1528
|
};
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
1529
|
+
const { exclude, include, override } = plugin.options;
|
|
1530
|
+
const optionsAreStatic = !exclude?.length && !include?.length && !override?.length;
|
|
1531
|
+
const allowedSchemaNames = allowedSchemaNamesByPlugin.get(plugin.name) ?? null;
|
|
1532
|
+
let error = null;
|
|
1533
|
+
const resolveForPlugin = (node) => {
|
|
1534
|
+
const transformedNode = transforms.applyTo(plugin.name, node);
|
|
1535
|
+
if (optionsAreStatic) return {
|
|
1536
|
+
transformedNode,
|
|
1537
|
+
options: plugin.options
|
|
1538
|
+
};
|
|
1539
|
+
const options = generatorContext.resolver.default.options(transformedNode, {
|
|
1540
|
+
options: plugin.options,
|
|
1541
|
+
exclude,
|
|
1542
|
+
include,
|
|
1543
|
+
override
|
|
1544
|
+
});
|
|
1545
|
+
if (options === null) return null;
|
|
1546
|
+
return {
|
|
1547
|
+
transformedNode,
|
|
1833
1548
|
options
|
|
1834
1549
|
};
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
const
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
1550
|
+
};
|
|
1551
|
+
if (emitsSchemaHook) for (const node of schemas) {
|
|
1552
|
+
if (error) break;
|
|
1553
|
+
try {
|
|
1554
|
+
const resolved = resolveForPlugin(node);
|
|
1555
|
+
if (!resolved) continue;
|
|
1556
|
+
const { transformedNode, options } = resolved;
|
|
1557
|
+
if (allowedSchemaNames !== null && transformedNode.name && !allowedSchemaNames.has(transformedNode.name)) continue;
|
|
1558
|
+
await this.hooks.emit("kubb:generate:schema", transformedNode, {
|
|
1559
|
+
...generatorContext,
|
|
1560
|
+
options
|
|
1843
1561
|
});
|
|
1844
|
-
|
|
1562
|
+
} catch (caughtError) {
|
|
1563
|
+
error = caughtError;
|
|
1845
1564
|
}
|
|
1846
|
-
if (dispatch.emit) await dispatch.emit(transformedNode, ctx);
|
|
1847
|
-
} catch (caughtError) {
|
|
1848
|
-
state.failed = true;
|
|
1849
|
-
state.error = caughtError;
|
|
1850
1565
|
}
|
|
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();
|
|
1566
|
+
if (emitsOperationHook) for (const node of operations) {
|
|
1567
|
+
if (error) break;
|
|
1568
|
+
try {
|
|
1569
|
+
const resolved = resolveForPlugin(node);
|
|
1570
|
+
if (!resolved) continue;
|
|
1571
|
+
await this.hooks.emit("kubb:generate:operation", resolved.transformedNode, {
|
|
1572
|
+
...generatorContext,
|
|
1573
|
+
options: resolved.options
|
|
1574
|
+
});
|
|
1575
|
+
} catch (caughtError) {
|
|
1576
|
+
error = caughtError;
|
|
1869
1577
|
}
|
|
1870
1578
|
}
|
|
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;
|
|
1579
|
+
if (!error && emitsOperationsHook) try {
|
|
1879
1580
|
const ctx = {
|
|
1880
1581
|
...generatorContext,
|
|
1881
1582
|
options: plugin.options
|
|
1882
1583
|
};
|
|
1883
|
-
const pluginOperations =
|
|
1884
|
-
const resolved = resolveForPlugin(
|
|
1584
|
+
const pluginOperations = operations.reduce((acc, node) => {
|
|
1585
|
+
const resolved = resolveForPlugin(node);
|
|
1885
1586
|
if (resolved) acc.push(resolved.transformedNode);
|
|
1886
1587
|
return acc;
|
|
1887
1588
|
}, []);
|
|
1888
|
-
for (const gen of generators) {
|
|
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
1589
|
await this.hooks.emit("kubb:generate:operations", pluginOperations, ctx);
|
|
1897
1590
|
} catch (caughtError) {
|
|
1898
|
-
|
|
1899
|
-
state.error = caughtError;
|
|
1591
|
+
error = caughtError;
|
|
1900
1592
|
}
|
|
1901
|
-
const duration = getElapsedMs(
|
|
1593
|
+
const duration = getElapsedMs(hrStart);
|
|
1902
1594
|
await this.#emitPluginEnd({
|
|
1903
|
-
plugin
|
|
1595
|
+
plugin,
|
|
1904
1596
|
duration,
|
|
1905
|
-
success: !
|
|
1906
|
-
error:
|
|
1597
|
+
success: !error,
|
|
1598
|
+
error: error ?? void 0
|
|
1907
1599
|
});
|
|
1908
|
-
if (
|
|
1909
|
-
...Diagnostics.from(
|
|
1910
|
-
plugin:
|
|
1600
|
+
if (error) diagnostics.push({
|
|
1601
|
+
...Diagnostics.from(error),
|
|
1602
|
+
plugin: plugin.name
|
|
1911
1603
|
});
|
|
1912
1604
|
diagnostics.push(Diagnostics.performance({
|
|
1913
|
-
plugin:
|
|
1605
|
+
plugin: plugin.name,
|
|
1914
1606
|
duration
|
|
1915
1607
|
}));
|
|
1916
1608
|
}
|
|
@@ -1938,10 +1630,6 @@ var KubbDriver = class {
|
|
|
1938
1630
|
}
|
|
1939
1631
|
if (!renderer) return;
|
|
1940
1632
|
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
1633
|
await instance.render(result);
|
|
1946
1634
|
this.fileManager.upsert(...instance.files);
|
|
1947
1635
|
} catch (_) {
|
|
@@ -1970,26 +1658,21 @@ var KubbDriver = class {
|
|
|
1970
1658
|
[Symbol.dispose]() {
|
|
1971
1659
|
this.dispose();
|
|
1972
1660
|
}
|
|
1973
|
-
#getDefaultResolver = memoize(this.#defaultResolvers, (pluginName) =>
|
|
1974
|
-
name: "default",
|
|
1975
|
-
pluginName
|
|
1976
|
-
})));
|
|
1661
|
+
#getDefaultResolver = memoize(this.#defaultResolvers, (pluginName) => createResolver({ pluginName }));
|
|
1977
1662
|
/**
|
|
1978
1663
|
* Merges `partial` with the plugin's default resolver and stores the result.
|
|
1979
1664
|
* Also mirrors it onto `plugin.resolver` so callers using `getPlugin(name).resolver`
|
|
1980
1665
|
* get the up-to-date resolver without going through `getResolver()`.
|
|
1981
1666
|
*/
|
|
1982
1667
|
setPluginResolver(pluginName, partial) {
|
|
1983
|
-
const
|
|
1984
|
-
|
|
1985
|
-
...partial
|
|
1986
|
-
};
|
|
1668
|
+
const defaultResolver = this.#getDefaultResolver(pluginName);
|
|
1669
|
+
const merged = Resolver.merge(defaultResolver, partial);
|
|
1987
1670
|
this.#resolvers.set(pluginName, merged);
|
|
1988
1671
|
const plugin = this.plugins.get(pluginName);
|
|
1989
1672
|
if (plugin) plugin.resolver = merged;
|
|
1990
1673
|
}
|
|
1991
1674
|
getResolver(pluginName) {
|
|
1992
|
-
return this.#resolvers.get(pluginName) ?? this
|
|
1675
|
+
return this.#resolvers.get(pluginName) ?? this.#getDefaultResolver(pluginName);
|
|
1993
1676
|
}
|
|
1994
1677
|
getContext(plugin) {
|
|
1995
1678
|
const driver = this;
|
|
@@ -2094,7 +1777,73 @@ function inputToAdapterSource(config) {
|
|
|
2094
1777
|
};
|
|
2095
1778
|
}
|
|
2096
1779
|
//#endregion
|
|
1780
|
+
//#region src/createStorage.ts
|
|
1781
|
+
/**
|
|
1782
|
+
* Defines a custom storage backend. The builder receives user options and
|
|
1783
|
+
* returns a `Storage` implementation. Kubb ships with filesystem and in-memory
|
|
1784
|
+
* storages. A custom backend writes generated files elsewhere, such as cloud
|
|
1785
|
+
* storage or a database.
|
|
1786
|
+
*
|
|
1787
|
+
* @example In-memory storage (the built-in implementation)
|
|
1788
|
+
* ```ts
|
|
1789
|
+
* import { createStorage } from '@kubb/core'
|
|
1790
|
+
*
|
|
1791
|
+
* export const memoryStorage = createStorage(() => {
|
|
1792
|
+
* const store = new Map<string, string>()
|
|
1793
|
+
*
|
|
1794
|
+
* return {
|
|
1795
|
+
* name: 'memory',
|
|
1796
|
+
* async hasItem(key) {
|
|
1797
|
+
* return store.has(key)
|
|
1798
|
+
* },
|
|
1799
|
+
* async getItem(key) {
|
|
1800
|
+
* return store.get(key) ?? null
|
|
1801
|
+
* },
|
|
1802
|
+
* async setItem(key, value) {
|
|
1803
|
+
* store.set(key, value)
|
|
1804
|
+
* },
|
|
1805
|
+
* async removeItem(key) {
|
|
1806
|
+
* store.delete(key)
|
|
1807
|
+
* },
|
|
1808
|
+
* async getKeys(base) {
|
|
1809
|
+
* const keys = [...store.keys()]
|
|
1810
|
+
* return base ? keys.filter((k) => k.startsWith(base)) : keys
|
|
1811
|
+
* },
|
|
1812
|
+
* async clear(base) {
|
|
1813
|
+
* if (!base) store.clear()
|
|
1814
|
+
* },
|
|
1815
|
+
* }
|
|
1816
|
+
* })
|
|
1817
|
+
* ```
|
|
1818
|
+
*/
|
|
1819
|
+
function createStorage(build) {
|
|
1820
|
+
return (options) => build(options ?? {});
|
|
1821
|
+
}
|
|
1822
|
+
//#endregion
|
|
2097
1823
|
//#region src/storages/fsStorage.ts
|
|
1824
|
+
const WRITE_CONCURRENCY = 50;
|
|
1825
|
+
function createLimiter(concurrency) {
|
|
1826
|
+
let active = 0;
|
|
1827
|
+
const queue = [];
|
|
1828
|
+
function next() {
|
|
1829
|
+
if (active >= concurrency) return;
|
|
1830
|
+
const run = queue.shift();
|
|
1831
|
+
if (!run) return;
|
|
1832
|
+
active++;
|
|
1833
|
+
run();
|
|
1834
|
+
}
|
|
1835
|
+
return function limit(task) {
|
|
1836
|
+
return new Promise((resolve, reject) => {
|
|
1837
|
+
queue.push(() => {
|
|
1838
|
+
task().then(resolve, reject).finally(() => {
|
|
1839
|
+
active--;
|
|
1840
|
+
next();
|
|
1841
|
+
});
|
|
1842
|
+
});
|
|
1843
|
+
next();
|
|
1844
|
+
});
|
|
1845
|
+
};
|
|
1846
|
+
}
|
|
2098
1847
|
/**
|
|
2099
1848
|
* Built-in filesystem storage driver.
|
|
2100
1849
|
*
|
|
@@ -2107,6 +1856,8 @@ function inputToAdapterSource(config) {
|
|
|
2107
1856
|
* - the write is skipped when the file content is already identical
|
|
2108
1857
|
* - missing parent directories are created automatically
|
|
2109
1858
|
* - Bun's native file API is used when running under Bun
|
|
1859
|
+
* - concurrent `setItem` calls are capped at {@link WRITE_CONCURRENCY} in flight, so a caller
|
|
1860
|
+
* can fire every file's write without pacing itself
|
|
2110
1861
|
*
|
|
2111
1862
|
* @example
|
|
2112
1863
|
* ```ts
|
|
@@ -2120,91 +1871,50 @@ function inputToAdapterSource(config) {
|
|
|
2120
1871
|
* })
|
|
2121
1872
|
* ```
|
|
2122
1873
|
*/
|
|
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`,
|
|
1874
|
+
const fsStorage = createStorage(() => {
|
|
1875
|
+
const limit = createLimiter(WRITE_CONCURRENCY);
|
|
1876
|
+
return {
|
|
1877
|
+
name: "fs",
|
|
2182
1878
|
async hasItem(key) {
|
|
2183
|
-
|
|
1879
|
+
try {
|
|
1880
|
+
await access(resolve(key));
|
|
1881
|
+
return true;
|
|
1882
|
+
} catch (_error) {
|
|
1883
|
+
return false;
|
|
1884
|
+
}
|
|
2184
1885
|
},
|
|
2185
1886
|
async getItem(key) {
|
|
2186
|
-
|
|
1887
|
+
try {
|
|
1888
|
+
return await readFile(resolve(key), "utf8");
|
|
1889
|
+
} catch (_error) {
|
|
1890
|
+
return null;
|
|
1891
|
+
}
|
|
2187
1892
|
},
|
|
2188
1893
|
async setItem(key, value) {
|
|
2189
|
-
|
|
2190
|
-
await storage.setItem(key, value);
|
|
1894
|
+
await limit(() => write(resolve(key), value, { sanity: false }));
|
|
2191
1895
|
},
|
|
2192
1896
|
async removeItem(key) {
|
|
2193
|
-
|
|
2194
|
-
await storage.removeItem(key);
|
|
1897
|
+
await rm(resolve(key), { force: true });
|
|
2195
1898
|
},
|
|
2196
1899
|
async getKeys(base) {
|
|
2197
|
-
|
|
2198
|
-
const
|
|
2199
|
-
|
|
2200
|
-
|
|
1900
|
+
const resolvedBase = resolve(base ?? process.cwd());
|
|
1901
|
+
const keys = [];
|
|
1902
|
+
try {
|
|
1903
|
+
for await (const entry of glob("**/*", {
|
|
1904
|
+
cwd: resolvedBase,
|
|
1905
|
+
withFileTypes: true
|
|
1906
|
+
})) if (entry.isFile()) keys.push(toPosixPath(relative(resolvedBase, join(entry.parentPath, entry.name))));
|
|
1907
|
+
} catch (_error) {}
|
|
1908
|
+
return keys;
|
|
2201
1909
|
},
|
|
2202
|
-
async clear() {
|
|
2203
|
-
|
|
2204
|
-
await
|
|
1910
|
+
async clear(base) {
|
|
1911
|
+
if (!base) return;
|
|
1912
|
+
await clean(resolve(base));
|
|
2205
1913
|
}
|
|
2206
|
-
}
|
|
2207
|
-
}
|
|
1914
|
+
};
|
|
1915
|
+
});
|
|
1916
|
+
//#endregion
|
|
1917
|
+
//#region src/createKubb.ts
|
|
2208
1918
|
function resolveConfig(userConfig) {
|
|
2209
1919
|
return {
|
|
2210
1920
|
...userConfig,
|
|
@@ -2262,12 +1972,11 @@ var Kubb = class {
|
|
|
2262
1972
|
async setup() {
|
|
2263
1973
|
const config = this.config;
|
|
2264
1974
|
const driver = new KubbDriver(config, { hooks: this.hooks });
|
|
2265
|
-
const storage = createSourcesView(config.storage);
|
|
2266
1975
|
this.hooks.setMaxListeners(Math.max(10, config.plugins.length * 4));
|
|
2267
1976
|
if (config.output.clean) await config.storage.clear(resolve(config.root, config.output.path));
|
|
2268
1977
|
await driver.setup();
|
|
2269
1978
|
this.#driver = driver;
|
|
2270
|
-
this.#storage = storage;
|
|
1979
|
+
this.#storage = config.storage;
|
|
2271
1980
|
}
|
|
2272
1981
|
/**
|
|
2273
1982
|
* Runs the full pipeline and throws on any plugin error.
|
|
@@ -2372,22 +2081,19 @@ const logLevel = {
|
|
|
2372
2081
|
* ```
|
|
2373
2082
|
*/
|
|
2374
2083
|
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 = [];
|
|
2084
|
+
const reports = /* @__PURE__ */ new Set();
|
|
2383
2085
|
return {
|
|
2384
2086
|
name: reporter.name,
|
|
2385
2087
|
async report(result, context) {
|
|
2386
|
-
|
|
2088
|
+
const report = await reporter.report(result, context);
|
|
2089
|
+
reports.add(report);
|
|
2387
2090
|
},
|
|
2388
2091
|
async drain(context) {
|
|
2389
|
-
await drain(context, reports);
|
|
2390
|
-
reports.
|
|
2092
|
+
await reporter.drain?.(context, Array.from(reports));
|
|
2093
|
+
reports.clear();
|
|
2094
|
+
},
|
|
2095
|
+
[Symbol.dispose]() {
|
|
2096
|
+
reports.clear();
|
|
2391
2097
|
}
|
|
2392
2098
|
};
|
|
2393
2099
|
}
|
|
@@ -2678,6 +2384,56 @@ function defineParser(parser) {
|
|
|
2678
2384
|
return parser;
|
|
2679
2385
|
}
|
|
2680
2386
|
//#endregion
|
|
2681
|
-
|
|
2387
|
+
//#region src/storages/memoryStorage.ts
|
|
2388
|
+
/**
|
|
2389
|
+
* In-memory storage driver. Useful for testing and dry-run scenarios where
|
|
2390
|
+
* generated output should be captured without touching the filesystem.
|
|
2391
|
+
*
|
|
2392
|
+
* All data lives in a `Map` scoped to the storage instance and is discarded
|
|
2393
|
+
* when the instance is garbage-collected.
|
|
2394
|
+
*
|
|
2395
|
+
* @example
|
|
2396
|
+
* ```ts
|
|
2397
|
+
* import { memoryStorage } from '@kubb/core'
|
|
2398
|
+
* import { defineConfig } from 'kubb'
|
|
2399
|
+
*
|
|
2400
|
+
* export default defineConfig({
|
|
2401
|
+
* input: { path: './petStore.yaml' },
|
|
2402
|
+
* output: { path: './src/gen' },
|
|
2403
|
+
* storage: memoryStorage(),
|
|
2404
|
+
* })
|
|
2405
|
+
* ```
|
|
2406
|
+
*/
|
|
2407
|
+
const memoryStorage = createStorage(() => {
|
|
2408
|
+
const store = /* @__PURE__ */ new Map();
|
|
2409
|
+
return {
|
|
2410
|
+
name: "memory",
|
|
2411
|
+
async hasItem(key) {
|
|
2412
|
+
return store.has(key);
|
|
2413
|
+
},
|
|
2414
|
+
async getItem(key) {
|
|
2415
|
+
return store.get(key) ?? null;
|
|
2416
|
+
},
|
|
2417
|
+
async setItem(key, value) {
|
|
2418
|
+
store.set(key, value);
|
|
2419
|
+
},
|
|
2420
|
+
async removeItem(key) {
|
|
2421
|
+
store.delete(key);
|
|
2422
|
+
},
|
|
2423
|
+
async getKeys(base) {
|
|
2424
|
+
const keys = [...store.keys()];
|
|
2425
|
+
return base ? keys.filter((k) => k.startsWith(base)) : keys;
|
|
2426
|
+
},
|
|
2427
|
+
async clear(base) {
|
|
2428
|
+
if (!base) {
|
|
2429
|
+
store.clear();
|
|
2430
|
+
return;
|
|
2431
|
+
}
|
|
2432
|
+
for (const key of store.keys()) if (key.startsWith(base)) store.delete(key);
|
|
2433
|
+
}
|
|
2434
|
+
};
|
|
2435
|
+
});
|
|
2436
|
+
//#endregion
|
|
2437
|
+
export { AsyncEventEmitter, Diagnostics, KubbDriver, Resolver, cliReporter, createAdapter, createKubb, createRenderer, createReporter, createResolver, createStorage, defineGenerator, defineParser, definePlugin, fileReporter, fsStorage, jsonReporter, logLevel, memoryStorage };
|
|
2682
2438
|
|
|
2683
2439
|
//# sourceMappingURL=index.js.map
|