@kubb/core 5.0.0-beta.98 → 5.0.0
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/index.cjs +526 -372
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +5 -4
- package/dist/index.js +502 -347
- package/dist/index.js.map +1 -1
- package/dist/mocks.cjs +10 -5
- package/dist/mocks.cjs.map +1 -1
- package/dist/mocks.d.ts +1 -1
- package/dist/mocks.js +10 -5
- package/dist/mocks.js.map +1 -1
- package/dist/{types-CK6CfipY.d.ts → types-Ba5Mo-G8.d.ts} +226 -48
- package/dist/{usingCtx-CZyLSqds.cjs → usingCtx-BdYw7ICK.cjs} +279 -30
- package/dist/usingCtx-BdYw7ICK.cjs.map +1 -0
- package/dist/{usingCtx-BNggxUEL.js → usingCtx-njZUKKsY.js} +255 -30
- package/dist/usingCtx-njZUKKsY.js.map +1 -0
- package/package.json +3 -3
- package/dist/usingCtx-BNggxUEL.js.map +0 -1
- package/dist/usingCtx-CZyLSqds.cjs.map +0 -1
package/dist/index.cjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
-
const require_usingCtx = require("./usingCtx-
|
|
2
|
+
const require_usingCtx = require("./usingCtx-BdYw7ICK.cjs");
|
|
3
3
|
let node_async_hooks = require("node:async_hooks");
|
|
4
4
|
let node_util = require("node:util");
|
|
5
5
|
let node_crypto = require("node:crypto");
|
|
@@ -7,6 +7,8 @@ let node_fs_promises = require("node:fs/promises");
|
|
|
7
7
|
let node_path = require("node:path");
|
|
8
8
|
node_path = require_usingCtx.__toESM(node_path, 1);
|
|
9
9
|
let _kubb_ast = require("@kubb/ast");
|
|
10
|
+
let node_fs = require("node:fs");
|
|
11
|
+
let node_os = require("node:os");
|
|
10
12
|
let node_process = require("node:process");
|
|
11
13
|
node_process = require_usingCtx.__toESM(node_process, 1);
|
|
12
14
|
//#region src/createAdapter.ts
|
|
@@ -147,163 +149,15 @@ const randomColors = [
|
|
|
147
149
|
function randomCliColor(text) {
|
|
148
150
|
if (!text) return "";
|
|
149
151
|
const index = (0, node_crypto.hash)("sha256", text, "buffer").readUInt32BE(0) % randomColors.length;
|
|
150
|
-
|
|
152
|
+
const color = randomColors[index] ?? "white";
|
|
153
|
+
return (0, node_util.styleText)(color, text);
|
|
151
154
|
}
|
|
152
155
|
//#endregion
|
|
153
|
-
//#region ../../internals/utils/src/promise.ts
|
|
154
|
-
/**
|
|
155
|
-
* Wraps `factory` with a keyed cache backed by the provided store.
|
|
156
|
-
*
|
|
157
|
-
* Pass a `WeakMap` for object keys (results are GC-eligible when the key is
|
|
158
|
-
* collected) or a `Map` for primitive keys. For multi-argument functions,
|
|
159
|
-
* nest two `memoize` calls — the outer keyed by the first argument, the
|
|
160
|
-
* inner (created once per outer miss) keyed by the second.
|
|
161
|
-
*
|
|
162
|
-
* Because the cache is owned by the caller, it can be shared, inspected, or
|
|
163
|
-
* cleared independently of the memoized function.
|
|
164
|
-
*
|
|
165
|
-
* @example Single WeakMap key
|
|
166
|
-
* ```ts
|
|
167
|
-
* const cache = new WeakMap<SchemaNode, Set<string>>()
|
|
168
|
-
* const getRefs = memoize(cache, (node) => collectRefs(node))
|
|
169
|
-
* ```
|
|
170
|
-
*
|
|
171
|
-
* @example Single Map key (primitive)
|
|
172
|
-
* ```ts
|
|
173
|
-
* const cache = new Map<string, Resolver>()
|
|
174
|
-
* const getResolver = memoize(cache, (name) => buildResolver(name))
|
|
175
|
-
* ```
|
|
176
|
-
*
|
|
177
|
-
* @example Two-level (object + primitive)
|
|
178
|
-
* ```ts
|
|
179
|
-
* const outer = new WeakMap<Params[], Map<string, Params[]>>()
|
|
180
|
-
* const fn = memoize(outer, (params) => memoize(new Map(), (key) => transform(params, key)))
|
|
181
|
-
* fn(params)('camelcase')
|
|
182
|
-
* ```
|
|
183
|
-
*/
|
|
184
|
-
function memoize(store, factory) {
|
|
185
|
-
return (key) => {
|
|
186
|
-
if (store.has(key)) return store.get(key);
|
|
187
|
-
const value = factory(key);
|
|
188
|
-
store.set(key, value);
|
|
189
|
-
return value;
|
|
190
|
-
};
|
|
191
|
-
}
|
|
192
|
-
//#endregion
|
|
193
|
-
//#region package.json
|
|
194
|
-
var version = "5.0.0-beta.98";
|
|
195
|
-
//#endregion
|
|
196
|
-
//#region src/constants.ts
|
|
197
|
-
/**
|
|
198
|
-
* Plugin `include` filter types that select operations directly. When one of these is set
|
|
199
|
-
* without a `schemaName` include, the generate phase pre-scans operations to compute the set
|
|
200
|
-
* of schemas they reach, so unreachable schemas can be pruned for that plugin.
|
|
201
|
-
*/
|
|
202
|
-
const OPERATION_FILTER_TYPES = /* @__PURE__ */ new Set([
|
|
203
|
-
"tag",
|
|
204
|
-
"operationId",
|
|
205
|
-
"path",
|
|
206
|
-
"method",
|
|
207
|
-
"contentType"
|
|
208
|
-
]);
|
|
209
|
-
/**
|
|
210
|
-
* Stable codes Kubb attaches to a `Diagnostic`. Each maps to a known failure mode
|
|
211
|
-
* and stays stable so it can be referenced in tooling and (later) docs. Reference
|
|
212
|
-
* these instead of inlining the string at a throw site.
|
|
213
|
-
*/
|
|
214
|
-
const diagnosticCode = {
|
|
215
|
-
/**
|
|
216
|
-
* Fallback for an unstructured error with no specific code.
|
|
217
|
-
*/
|
|
218
|
-
unknown: "KUBB_UNKNOWN",
|
|
219
|
-
/**
|
|
220
|
-
* The file or URL set as `input` could not be read.
|
|
221
|
-
*/
|
|
222
|
-
inputNotFound: "KUBB_INPUT_NOT_FOUND",
|
|
223
|
-
/**
|
|
224
|
-
* An adapter was configured without an `input`.
|
|
225
|
-
*/
|
|
226
|
-
inputRequired: "KUBB_INPUT_REQUIRED",
|
|
227
|
-
/**
|
|
228
|
-
* A `$ref` (or equivalent reference) could not be resolved in the source document.
|
|
229
|
-
*/
|
|
230
|
-
refNotFound: "KUBB_REF_NOT_FOUND",
|
|
231
|
-
/**
|
|
232
|
-
* A server variable value is not allowed by its `enum`.
|
|
233
|
-
*/
|
|
234
|
-
invalidServerVariable: "KUBB_INVALID_SERVER_VARIABLE",
|
|
235
|
-
/**
|
|
236
|
-
* A required plugin is missing from the config.
|
|
237
|
-
*/
|
|
238
|
-
pluginNotFound: "KUBB_PLUGIN_NOT_FOUND",
|
|
239
|
-
/**
|
|
240
|
-
* A plugin threw while generating.
|
|
241
|
-
*/
|
|
242
|
-
pluginFailed: "KUBB_PLUGIN_FAILED",
|
|
243
|
-
/**
|
|
244
|
-
* A plugin reported a non-fatal warning through `ctx.warn`.
|
|
245
|
-
*/
|
|
246
|
-
pluginWarning: "KUBB_PLUGIN_WARNING",
|
|
247
|
-
/**
|
|
248
|
-
* A plugin reported an informational message through `ctx.info`.
|
|
249
|
-
*/
|
|
250
|
-
pluginInfo: "KUBB_PLUGIN_INFO",
|
|
251
|
-
/**
|
|
252
|
-
* A schema uses a `format` Kubb does not map to a specific type. Reserved for
|
|
253
|
-
* adapters to emit as a `warning`.
|
|
254
|
-
*/
|
|
255
|
-
unsupportedFormat: "KUBB_UNSUPPORTED_FORMAT",
|
|
256
|
-
/**
|
|
257
|
-
* A referenced schema or operation is marked `deprecated`. Reserved for adapters
|
|
258
|
-
* to emit as an `info`.
|
|
259
|
-
*/
|
|
260
|
-
deprecated: "KUBB_DEPRECATED",
|
|
261
|
-
/**
|
|
262
|
-
* An adapter is required but the config has none. The build cannot read the input
|
|
263
|
-
* without one.
|
|
264
|
-
*/
|
|
265
|
-
adapterRequired: "KUBB_ADAPTER_REQUIRED",
|
|
266
|
-
/**
|
|
267
|
-
* A resolved output path escapes the output directory, which can stem from a path
|
|
268
|
-
* traversal in the spec or a misconfigured `group.name`.
|
|
269
|
-
*/
|
|
270
|
-
pathTraversal: "KUBB_PATH_TRAVERSAL",
|
|
271
|
-
/**
|
|
272
|
-
* `output.clean` is enabled but `output.path` resolves to the project root or a parent of it,
|
|
273
|
-
* so cleaning would delete kubb.config and every source file.
|
|
274
|
-
*/
|
|
275
|
-
cleanRoot: "KUBB_CLEAN_ROOT",
|
|
276
|
-
/**
|
|
277
|
-
* A plugin's options are invalid, for example `output.mode: 'file'` paired with a `group` option.
|
|
278
|
-
*/
|
|
279
|
-
invalidPluginOptions: "KUBB_INVALID_PLUGIN_OPTIONS",
|
|
280
|
-
/**
|
|
281
|
-
* A post-generate command (`output.postGenerate`) exited with a failure.
|
|
282
|
-
*/
|
|
283
|
-
postGenerateFailed: "KUBB_POST_GENERATE_FAILED",
|
|
284
|
-
/**
|
|
285
|
-
* The formatter pass over the generated files failed.
|
|
286
|
-
*/
|
|
287
|
-
formatFailed: "KUBB_FORMAT_FAILED",
|
|
288
|
-
/**
|
|
289
|
-
* The linter pass over the generated files failed.
|
|
290
|
-
*/
|
|
291
|
-
lintFailed: "KUBB_LINT_FAILED",
|
|
292
|
-
/**
|
|
293
|
-
* Not a failure. Carries a plugin's elapsed time, summed into the run total.
|
|
294
|
-
*/
|
|
295
|
-
performance: "KUBB_PERFORMANCE",
|
|
296
|
-
/**
|
|
297
|
-
* Not a failure. A newer Kubb version is available on npm.
|
|
298
|
-
*/
|
|
299
|
-
updateAvailable: "KUBB_UPDATE_AVAILABLE"
|
|
300
|
-
};
|
|
301
|
-
//#endregion
|
|
302
156
|
//#region src/Diagnostics.ts
|
|
303
157
|
/**
|
|
304
158
|
* Docs major version, derived from the package version so the link tracks the published major.
|
|
305
159
|
*/
|
|
306
|
-
const docsMajor =
|
|
160
|
+
const docsMajor = "5.0.0".split(".")[0] ?? "5";
|
|
307
161
|
/**
|
|
308
162
|
* Builds a type guard that narrows a {@link Diagnostic} to the variant for `kind`. A diagnostic
|
|
309
163
|
* with no `kind` is treated as a `problem`.
|
|
@@ -356,102 +210,122 @@ const severityStyle = {
|
|
|
356
210
|
* and `Diagnostics.docsUrl` for the matching kubb.dev page.
|
|
357
211
|
*/
|
|
358
212
|
const diagnosticCatalog = {
|
|
359
|
-
[diagnosticCode.unknown]: {
|
|
213
|
+
[require_usingCtx.diagnosticCode.unknown]: {
|
|
360
214
|
title: "Unknown error",
|
|
361
215
|
cause: "An error was thrown without a stable Kubb code, so it is reported as-is.",
|
|
362
216
|
fix: "Read the underlying message and stack. If it comes from a plugin or adapter, check its configuration; otherwise report it as a possible Kubb bug."
|
|
363
217
|
},
|
|
364
|
-
[diagnosticCode.inputNotFound]: {
|
|
218
|
+
[require_usingCtx.diagnosticCode.inputNotFound]: {
|
|
365
219
|
title: "Input not found",
|
|
366
|
-
cause: "The file
|
|
367
|
-
fix: "Check that the path
|
|
220
|
+
cause: "The file set as `input` (or passed as `kubb generate PATH`) could not be read. A URL reports `KUBB_INPUT_REQUEST_FAILED` or `KUBB_INPUT_UNREACHABLE` instead.",
|
|
221
|
+
fix: "Check that the path exists and is readable, then set it as `input` or pass it on the CLI."
|
|
368
222
|
},
|
|
369
|
-
[diagnosticCode.
|
|
223
|
+
[require_usingCtx.diagnosticCode.inputRequestFailed]: {
|
|
224
|
+
title: "Input request failed",
|
|
225
|
+
cause: "A URL set as `input` (or reached through a `$ref`) answered with a 4xx or 5xx status instead of the document.",
|
|
226
|
+
fix: "Open the URL to see what the server returns. A 401 or 403 needs credentials Kubb does not send, so download the document and point `input` at the local file. A 404 means the path is wrong, and a 5xx means the server itself failed."
|
|
227
|
+
},
|
|
228
|
+
[require_usingCtx.diagnosticCode.inputUnreachable]: {
|
|
229
|
+
title: "Input unreachable",
|
|
230
|
+
cause: "A URL set as `input` (or reached through a `$ref`) never answered, so the request failed before a status came back. A refused connection, an unknown host, an expired certificate, and a timeout all land here.",
|
|
231
|
+
fix: "Check that the host is running and reachable from this machine. For a local server, start it and confirm the port matches the one in `input`."
|
|
232
|
+
},
|
|
233
|
+
[require_usingCtx.diagnosticCode.inputRequired]: {
|
|
370
234
|
title: "Input required",
|
|
371
235
|
cause: "An adapter is configured but no `input` was provided.",
|
|
372
236
|
fix: "Set `input` to a file path, a URL, an inline spec (JSON/YAML string), or a parsed object in your Kubb config."
|
|
373
237
|
},
|
|
374
|
-
[diagnosticCode.
|
|
238
|
+
[require_usingCtx.diagnosticCode.legacyInput]: {
|
|
239
|
+
title: "Legacy input shape",
|
|
240
|
+
cause: "`input` is a `{ path }` or `{ data }` wrapper, which v4 used to point at a document and v5 reads as the document itself.",
|
|
241
|
+
fix: "Unwrap it: `input: { path: \"./petStore.yaml\" }` becomes `input: \"./petStore.yaml\"`, and `input: { data: spec }` becomes `input: spec`."
|
|
242
|
+
},
|
|
243
|
+
[require_usingCtx.diagnosticCode.invalidDocument]: {
|
|
244
|
+
title: "Invalid document",
|
|
245
|
+
cause: "The parsed `input` has no `openapi` or `swagger` version field, so it is not an OpenAPI or Swagger document.",
|
|
246
|
+
fix: "Point `input` at a document that declares `openapi` or `swagger`, and check that a passed object is the spec itself rather than a wrapper around it."
|
|
247
|
+
},
|
|
248
|
+
[require_usingCtx.diagnosticCode.refNotFound]: {
|
|
375
249
|
title: "Reference not found",
|
|
376
250
|
cause: "A `$ref` could not be resolved in the source document.",
|
|
377
251
|
fix: "Add the missing definition (for example under `components.schemas`) or fix the `$ref`. Run `kubb validate` to check the spec."
|
|
378
252
|
},
|
|
379
|
-
[diagnosticCode.invalidServerVariable]: {
|
|
253
|
+
[require_usingCtx.diagnosticCode.invalidServerVariable]: {
|
|
380
254
|
title: "Invalid server variable",
|
|
381
255
|
cause: "A server variable value is not allowed by its `enum`.",
|
|
382
256
|
fix: "Use one of the values listed in the server variable `enum`, or update the spec."
|
|
383
257
|
},
|
|
384
|
-
[diagnosticCode.pluginNotFound]: {
|
|
258
|
+
[require_usingCtx.diagnosticCode.pluginNotFound]: {
|
|
385
259
|
title: "Plugin not found",
|
|
386
260
|
cause: "A plugin that another plugin depends on is missing from the config.",
|
|
387
261
|
fix: "Add the required plugin to the `plugins` array in kubb.config.ts, or remove the dependency on it."
|
|
388
262
|
},
|
|
389
|
-
[diagnosticCode.pluginFailed]: {
|
|
263
|
+
[require_usingCtx.diagnosticCode.pluginFailed]: {
|
|
390
264
|
title: "Plugin failed",
|
|
391
265
|
cause: "A plugin threw while generating, or reported an error through `ctx.error`.",
|
|
392
266
|
fix: "Read the underlying error and check the plugin options and the schema or operation it failed on."
|
|
393
267
|
},
|
|
394
|
-
[diagnosticCode.pluginWarning]: {
|
|
268
|
+
[require_usingCtx.diagnosticCode.pluginWarning]: {
|
|
395
269
|
title: "Plugin warning",
|
|
396
270
|
cause: "A plugin reported a non-fatal warning through `ctx.warn`.",
|
|
397
271
|
fix: "Review the message. It does not fail the build; adjust the plugin options or input if the warning is unwanted."
|
|
398
272
|
},
|
|
399
|
-
[diagnosticCode.pluginInfo]: {
|
|
273
|
+
[require_usingCtx.diagnosticCode.pluginInfo]: {
|
|
400
274
|
title: "Plugin info",
|
|
401
275
|
cause: "A plugin reported an informational message through `ctx.info`.",
|
|
402
276
|
fix: "Informational only. No action is required."
|
|
403
277
|
},
|
|
404
|
-
[diagnosticCode.unsupportedFormat]: {
|
|
278
|
+
[require_usingCtx.diagnosticCode.unsupportedFormat]: {
|
|
405
279
|
title: "Unsupported format",
|
|
406
280
|
cause: "A schema uses a `format` Kubb does not map to a specific type, so it falls back to the base type.",
|
|
407
281
|
fix: "Use a format Kubb supports, or handle the custom format with a parser or plugin."
|
|
408
282
|
},
|
|
409
|
-
[diagnosticCode.deprecated]: {
|
|
283
|
+
[require_usingCtx.diagnosticCode.deprecated]: {
|
|
410
284
|
title: "Deprecated",
|
|
411
285
|
cause: "A referenced schema or operation is marked `deprecated`.",
|
|
412
286
|
fix: "Migrate off the deprecated definition if the warning is unwanted."
|
|
413
287
|
},
|
|
414
|
-
[diagnosticCode.adapterRequired]: {
|
|
288
|
+
[require_usingCtx.diagnosticCode.adapterRequired]: {
|
|
415
289
|
title: "Adapter required",
|
|
416
290
|
cause: "An action needs an adapter but none is configured.",
|
|
417
291
|
fix: "Set `adapter` in kubb.config.ts, for example `adapterOas()`."
|
|
418
292
|
},
|
|
419
|
-
[diagnosticCode.pathTraversal]: {
|
|
293
|
+
[require_usingCtx.diagnosticCode.pathTraversal]: {
|
|
420
294
|
title: "Path traversal",
|
|
421
295
|
cause: "A resolved output path escaped the output directory, which can stem from a path traversal in the spec or a misconfigured `group.name`.",
|
|
422
296
|
fix: "Keep generated paths within the output directory. Review the `group.name` function and the names coming from the spec."
|
|
423
297
|
},
|
|
424
|
-
[diagnosticCode.cleanRoot]: {
|
|
298
|
+
[require_usingCtx.diagnosticCode.cleanRoot]: {
|
|
425
299
|
title: "Clean targets the project root",
|
|
426
300
|
cause: "`output.clean` is enabled and `output.path` resolves to the project root or a parent of it, so cleaning would delete `kubb.config` and every source file.",
|
|
427
301
|
fix: "Point `output.path` at a subdirectory such as `./src/gen` so clean only removes generated code, or disable `output.clean`."
|
|
428
302
|
},
|
|
429
|
-
[diagnosticCode.invalidPluginOptions]: {
|
|
303
|
+
[require_usingCtx.diagnosticCode.invalidPluginOptions]: {
|
|
430
304
|
title: "Invalid plugin options",
|
|
431
305
|
cause: "A plugin was configured with options that cannot be honored, for example `output.mode: 'file'` paired with a `group` option.",
|
|
432
306
|
fix: "Fix the plugin options. A single-file output has nothing to group, so remove the `group` option or use `output.mode: 'directory'`."
|
|
433
307
|
},
|
|
434
|
-
[diagnosticCode.postGenerateFailed]: {
|
|
308
|
+
[require_usingCtx.diagnosticCode.postGenerateFailed]: {
|
|
435
309
|
title: "Post-generate command failed",
|
|
436
310
|
cause: "A post-generate command (`output.postGenerate`) exited with a non-zero status.",
|
|
437
311
|
fix: "Check the command is installed and correct, and run it manually to see the error."
|
|
438
312
|
},
|
|
439
|
-
[diagnosticCode.formatFailed]: {
|
|
313
|
+
[require_usingCtx.diagnosticCode.formatFailed]: {
|
|
440
314
|
title: "Format failed",
|
|
441
315
|
cause: "The formatter pass over the generated files failed.",
|
|
442
316
|
fix: "Check the formatter (oxfmt, biome, or prettier) is installed and its config is valid, then run it manually on the output."
|
|
443
317
|
},
|
|
444
|
-
[diagnosticCode.lintFailed]: {
|
|
318
|
+
[require_usingCtx.diagnosticCode.lintFailed]: {
|
|
445
319
|
title: "Lint failed",
|
|
446
320
|
cause: "The linter pass over the generated files failed.",
|
|
447
321
|
fix: "Check the linter (oxlint, biome, or eslint) is installed and its config is valid, then run it manually on the output."
|
|
448
322
|
},
|
|
449
|
-
[diagnosticCode.performance]: {
|
|
323
|
+
[require_usingCtx.diagnosticCode.performance]: {
|
|
450
324
|
title: "Performance",
|
|
451
325
|
cause: "Not a failure. Records a plugin’s elapsed time, summed into the run total.",
|
|
452
326
|
fix: "No action. This is an informational metric."
|
|
453
327
|
},
|
|
454
|
-
[diagnosticCode.updateAvailable]: {
|
|
328
|
+
[require_usingCtx.diagnosticCode.updateAvailable]: {
|
|
455
329
|
title: "Update available",
|
|
456
330
|
cause: "A newer Kubb version is published on npm than the one running.",
|
|
457
331
|
fix: "Update the `@kubb/*` packages, for example `npm install -g @kubb/cli`, to get the latest fixes."
|
|
@@ -471,7 +345,7 @@ var Diagnostics = class Diagnostics {
|
|
|
471
345
|
/**
|
|
472
346
|
* The diagnostic code catalog, exposed as `Diagnostics.code` (e.g. `Diagnostics.code.refNotFound`).
|
|
473
347
|
*/
|
|
474
|
-
static code = diagnosticCode;
|
|
348
|
+
static code = require_usingCtx.diagnosticCode;
|
|
475
349
|
/**
|
|
476
350
|
* Type guard for a build {@link ProblemDiagnostic}.
|
|
477
351
|
*/
|
|
@@ -552,7 +426,7 @@ var Diagnostics = class Diagnostics {
|
|
|
552
426
|
current = current.cause;
|
|
553
427
|
}
|
|
554
428
|
return {
|
|
555
|
-
code: diagnosticCode.unknown,
|
|
429
|
+
code: require_usingCtx.diagnosticCode.unknown,
|
|
556
430
|
severity: "error",
|
|
557
431
|
message: root ? root.message : require_usingCtx.getErrorMessage(error),
|
|
558
432
|
cause: root
|
|
@@ -564,7 +438,7 @@ var Diagnostics = class Diagnostics {
|
|
|
564
438
|
static performance({ plugin, duration }) {
|
|
565
439
|
return {
|
|
566
440
|
kind: "performance",
|
|
567
|
-
code: diagnosticCode.performance,
|
|
441
|
+
code: require_usingCtx.diagnosticCode.performance,
|
|
568
442
|
severity: "info",
|
|
569
443
|
message: `${plugin} generated in ${Math.round(duration)}ms`,
|
|
570
444
|
plugin,
|
|
@@ -577,7 +451,7 @@ var Diagnostics = class Diagnostics {
|
|
|
577
451
|
static update({ currentVersion, latestVersion }) {
|
|
578
452
|
return {
|
|
579
453
|
kind: "update",
|
|
580
|
-
code: diagnosticCode.updateAvailable,
|
|
454
|
+
code: require_usingCtx.diagnosticCode.updateAvailable,
|
|
581
455
|
severity: "info",
|
|
582
456
|
message: `Update available: v${currentVersion} → v${latestVersion}. Run \`npm install -g @kubb/cli\` to update.`,
|
|
583
457
|
currentVersion,
|
|
@@ -670,7 +544,7 @@ var Diagnostics = class Diagnostics {
|
|
|
670
544
|
...problem?.location ? { location: problem.location } : {},
|
|
671
545
|
...problem?.help ? { help: problem.help } : {},
|
|
672
546
|
...problem?.plugin ? { plugin: problem.plugin } : {},
|
|
673
|
-
...diagnostic.code === diagnosticCode.unknown ? {} : { docsUrl: Diagnostics.docsUrl(diagnostic.code) }
|
|
547
|
+
...diagnostic.code === require_usingCtx.diagnosticCode.unknown ? {} : { docsUrl: Diagnostics.docsUrl(diagnostic.code) }
|
|
674
548
|
};
|
|
675
549
|
}
|
|
676
550
|
/**
|
|
@@ -690,7 +564,7 @@ var Diagnostics = class Diagnostics {
|
|
|
690
564
|
const details = [];
|
|
691
565
|
if (problem?.location && "pointer" in problem.location) details.push(` ${(0, node_util.styleText)("dim", "at:")} ${(0, node_util.styleText)("cyan", problem.location.pointer)}`);
|
|
692
566
|
if (problem?.help) details.push(` ${(0, node_util.styleText)("cyan", "fix:")} ${problem.help}`);
|
|
693
|
-
if (code !== diagnosticCode.unknown) details.push(` ${(0, node_util.styleText)("dim", "see:")} ${(0, node_util.styleText)("cyan", Diagnostics.docsUrl(code))}`);
|
|
567
|
+
if (code !== require_usingCtx.diagnosticCode.unknown) details.push(` ${(0, node_util.styleText)("dim", "see:")} ${(0, node_util.styleText)("cyan", Diagnostics.docsUrl(code))}`);
|
|
694
568
|
return {
|
|
695
569
|
headline,
|
|
696
570
|
details
|
|
@@ -711,14 +585,18 @@ var Diagnostics = class Diagnostics {
|
|
|
711
585
|
* Merges the `output.mode` default into the output config and validates the combination.
|
|
712
586
|
* Throws `KUBB_INVALID_PLUGIN_OPTIONS` when `mode: 'file'` is paired with a `group` option,
|
|
713
587
|
* since a single-file output has nothing to group.
|
|
588
|
+
*
|
|
589
|
+
* An omitted `mode` follows the shape of `path`: an extension means a single file, anything
|
|
590
|
+
* else is a directory. Plugin defaults such as `path: 'types'` name a directory, so they
|
|
591
|
+
* generate without the caller spelling out `mode`.
|
|
714
592
|
*/
|
|
715
593
|
function normalizeOutput({ output, group, pluginName }) {
|
|
716
|
-
const mode = output.mode ?? "directory";
|
|
594
|
+
const mode = output.mode ?? (node_path.default.extname(output.path) ? "file" : "directory");
|
|
717
595
|
if (mode === "file" && group) throw new Diagnostics.Error({
|
|
718
|
-
code: diagnosticCode.invalidPluginOptions,
|
|
596
|
+
code: require_usingCtx.diagnosticCode.invalidPluginOptions,
|
|
719
597
|
severity: "error",
|
|
720
|
-
message: `Plugin "${pluginName}"
|
|
721
|
-
help: "A single-file output has nothing to group. Remove the `group` option, or
|
|
598
|
+
message: `Plugin "${pluginName}" resolves \`output.mode\` to 'file' but also configures a \`group\` option.`,
|
|
599
|
+
help: "A single-file output has nothing to group. Remove the `group` option, give `output.path` an extensionless directory name, or set `output.mode: 'directory'` explicitly.",
|
|
722
600
|
location: { kind: "config" },
|
|
723
601
|
plugin: pluginName
|
|
724
602
|
});
|
|
@@ -769,6 +647,23 @@ function getInputKind(input) {
|
|
|
769
647
|
return "file";
|
|
770
648
|
}
|
|
771
649
|
/**
|
|
650
|
+
* The v4 `input` wrapper keys. v4 typed `input` as `{ path }` or `{ data }`; v5 takes the value
|
|
651
|
+
* directly, so the wrapper now matches the "already-parsed document" branch and silently yields
|
|
652
|
+
* an empty build.
|
|
653
|
+
*/
|
|
654
|
+
const legacyInputKeys = ["path", "data"];
|
|
655
|
+
/**
|
|
656
|
+
* Detects the v4 `{ path }` / `{ data }` wrapper so it fails loudly instead of being read as a
|
|
657
|
+
* document. A real spec always carries more than these keys, so an object whose keys are drawn
|
|
658
|
+
* only from them is the old shape rather than a document that happens to have a `path` property.
|
|
659
|
+
*/
|
|
660
|
+
function isLegacyInput(input) {
|
|
661
|
+
if (Array.isArray(input)) return input.some(isLegacyInput);
|
|
662
|
+
if (typeof input !== "object" || input === null) return false;
|
|
663
|
+
const keys = Object.keys(input);
|
|
664
|
+
return keys.length > 0 && keys.every((key) => legacyInputKeys.includes(key));
|
|
665
|
+
}
|
|
666
|
+
/**
|
|
772
667
|
* Normalizes `config.input` into an `AdapterSource` the adapter can parse.
|
|
773
668
|
*
|
|
774
669
|
* A parsed object and inline content become `{ type: 'data' }`; a URL is kept verbatim and a
|
|
@@ -776,6 +671,13 @@ function getInputKind(input) {
|
|
|
776
671
|
*/
|
|
777
672
|
function inputToAdapterSource(config) {
|
|
778
673
|
const input = config.input;
|
|
674
|
+
if (input && isLegacyInput(input)) throw new Diagnostics.Error({
|
|
675
|
+
code: Diagnostics.code.legacyInput,
|
|
676
|
+
severity: "error",
|
|
677
|
+
message: "The `input` option uses the v4 `{ path }` / `{ data }` wrapper.",
|
|
678
|
+
help: "Unwrap it: `input: { path: \"./petStore.yaml\" }` becomes `input: \"./petStore.yaml\"`, and `input: { data: spec }` becomes `input: spec`.",
|
|
679
|
+
location: { kind: "config" }
|
|
680
|
+
});
|
|
779
681
|
if (!input) throw new Diagnostics.Error({
|
|
780
682
|
code: Diagnostics.code.inputRequired,
|
|
781
683
|
severity: "error",
|
|
@@ -887,28 +789,24 @@ var Resolver = class Resolver {
|
|
|
887
789
|
* schemas (`targetName`) import the emitted name. Names and paths go through the top-level
|
|
888
790
|
* `name` and `file`, so import entries follow the plugin's conventions, and a per-call
|
|
889
791
|
* `name` override wins over both.
|
|
792
|
+
*
|
|
793
|
+
* The subtree scan runs through `collectImportedRefNames`, which memoizes by node identity, so a
|
|
794
|
+
* schema shared across the ts, zod, and faker plugins is walked once and every plugin's resolver
|
|
795
|
+
* reads the same ref set instead of re-scanning it per plugin.
|
|
890
796
|
*/
|
|
891
797
|
imports(options) {
|
|
892
798
|
const { node, root, output, group, extname = ".ts", name } = options;
|
|
893
799
|
const resolveName = name ?? ((schemaName) => this.name(schemaName));
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
name: schemaName,
|
|
905
|
-
extname,
|
|
906
|
-
root,
|
|
907
|
-
output,
|
|
908
|
-
group
|
|
909
|
-
}).path
|
|
910
|
-
});
|
|
911
|
-
} });
|
|
800
|
+
return (0, _kubb_ast.collectImportedRefNames)(node).map((schemaName) => _kubb_ast.ast.factory.createImport({
|
|
801
|
+
name: [resolveName(schemaName)],
|
|
802
|
+
path: this.file({
|
|
803
|
+
name: schemaName,
|
|
804
|
+
extname,
|
|
805
|
+
root,
|
|
806
|
+
output,
|
|
807
|
+
group
|
|
808
|
+
}).path
|
|
809
|
+
}));
|
|
912
810
|
}
|
|
913
811
|
/**
|
|
914
812
|
* Folds each `override` over `base`, left to right, and returns a new resolver with helpers
|
|
@@ -1029,12 +927,12 @@ var Resolver = class Resolver {
|
|
|
1029
927
|
return segment ? require_usingCtx.camelCase(segment) : "";
|
|
1030
928
|
}
|
|
1031
929
|
/**
|
|
1032
|
-
* `mode: 'file'` resolves directly to `output.path`. `mode: 'directory'`
|
|
930
|
+
* `mode: 'file'` (default) resolves directly to `output.path`. `mode: 'directory'` resolves
|
|
1033
931
|
* to `output.path/{baseName}`, or into a subdirectory when `group` and a `tag`/`path` value
|
|
1034
932
|
* are provided.
|
|
1035
933
|
*/
|
|
1036
934
|
#resolvePath({ baseName, tag, path: groupPath, root, output, group }) {
|
|
1037
|
-
if (output.mode
|
|
935
|
+
if (output.mode !== "directory") return node_path.default.resolve(root, output.path);
|
|
1038
936
|
const outputDir = node_path.default.resolve(root, output.path);
|
|
1039
937
|
const result = group && (groupPath || tag) ? node_path.default.resolve(outputDir, Resolver.#resolveGroupDir(group, group.type === "path" ? groupPath : tag), baseName) : node_path.default.resolve(outputDir, baseName);
|
|
1040
938
|
const outputDirWithSep = outputDir.endsWith(node_path.default.sep) ? outputDir : `${outputDir}${node_path.default.sep}`;
|
|
@@ -1288,6 +1186,38 @@ const ENFORCE_ORDER = {
|
|
|
1288
1186
|
post: 1
|
|
1289
1187
|
};
|
|
1290
1188
|
const enforceWeight = (plugin) => plugin.enforce ? ENFORCE_ORDER[plugin.enforce] : 0;
|
|
1189
|
+
/**
|
|
1190
|
+
* The options bag a `NormalizedPlugin` starts with before a plugin refines it: a directory output
|
|
1191
|
+
* at the plugin root and empty filter lists.
|
|
1192
|
+
*/
|
|
1193
|
+
function defaultPluginOptions() {
|
|
1194
|
+
return {
|
|
1195
|
+
output: {
|
|
1196
|
+
path: ".",
|
|
1197
|
+
mode: "directory"
|
|
1198
|
+
},
|
|
1199
|
+
exclude: [],
|
|
1200
|
+
override: []
|
|
1201
|
+
};
|
|
1202
|
+
}
|
|
1203
|
+
/**
|
|
1204
|
+
* Fills in the `output`, `exclude`, and `override` a `NormalizedPlugin` needs from a plugin's raw
|
|
1205
|
+
* options, running `output` through `normalizeOutput`. Idempotent, so the driver can apply it after
|
|
1206
|
+
* `setOptions` has already run without disturbing an already-normalized bag.
|
|
1207
|
+
*/
|
|
1208
|
+
function normalizePluginOptions(rawOptions, pluginName) {
|
|
1209
|
+
const options = {
|
|
1210
|
+
...defaultPluginOptions(),
|
|
1211
|
+
...rawOptions ?? {}
|
|
1212
|
+
};
|
|
1213
|
+
const group = "group" in options ? options.group : void 0;
|
|
1214
|
+
options.output = normalizeOutput({
|
|
1215
|
+
output: options.output,
|
|
1216
|
+
group,
|
|
1217
|
+
pluginName
|
|
1218
|
+
});
|
|
1219
|
+
return options;
|
|
1220
|
+
}
|
|
1291
1221
|
var KubbDriver = class {
|
|
1292
1222
|
config;
|
|
1293
1223
|
options;
|
|
@@ -1309,13 +1239,6 @@ var KubbDriver = class {
|
|
|
1309
1239
|
fileManager = new require_usingCtx.FileManager();
|
|
1310
1240
|
plugins = /* @__PURE__ */ new Map();
|
|
1311
1241
|
/**
|
|
1312
|
-
* Tracks which plugins have generators registered via `addGenerator()` (hook-based path).
|
|
1313
|
-
* Used by the build loop to decide whether to emit generator hooks for a given plugin.
|
|
1314
|
-
*/
|
|
1315
|
-
#hookGeneratorPlugins = /* @__PURE__ */ new Set();
|
|
1316
|
-
#resolvers = /* @__PURE__ */ new Map();
|
|
1317
|
-
#defaultResolvers = /* @__PURE__ */ new Map();
|
|
1318
|
-
/**
|
|
1319
1242
|
* Removers for every listener the driver added (plugin, generator) so `dispose()` can detach
|
|
1320
1243
|
* them in one pass. External `hooks.hook(...)` listeners are not tracked.
|
|
1321
1244
|
*/
|
|
@@ -1343,15 +1266,8 @@ var KubbDriver = class {
|
|
|
1343
1266
|
dependencies: rawPlugin.dependencies,
|
|
1344
1267
|
enforce: rawPlugin.enforce,
|
|
1345
1268
|
hooks: rawPlugin.hooks,
|
|
1346
|
-
options: rawPlugin.options ??
|
|
1347
|
-
|
|
1348
|
-
path: ".",
|
|
1349
|
-
mode: "directory"
|
|
1350
|
-
},
|
|
1351
|
-
exclude: [],
|
|
1352
|
-
override: []
|
|
1353
|
-
},
|
|
1354
|
-
resolver: this.#getDefaultResolver(rawPlugin.name)
|
|
1269
|
+
options: rawPlugin.options ?? defaultPluginOptions(),
|
|
1270
|
+
resolver: createResolver({ pluginName: rawPlugin.name })
|
|
1355
1271
|
};
|
|
1356
1272
|
}));
|
|
1357
1273
|
for (const plugin of normalized) {
|
|
@@ -1460,46 +1376,30 @@ var KubbDriver = class {
|
|
|
1460
1376
|
}
|
|
1461
1377
|
}
|
|
1462
1378
|
/**
|
|
1463
|
-
*
|
|
1464
|
-
*
|
|
1465
|
-
* The generator's `schema`, `operation`, and `operations` methods are registered as
|
|
1466
|
-
* listeners on `kubb:generate:schema`, `kubb:generate:operation`, and `kubb:generate:operations`
|
|
1467
|
-
* respectively. Each listener is scoped to the owning plugin via a `ctx.plugin.name` check
|
|
1468
|
-
* so that generators from different plugins do not cross-fire.
|
|
1379
|
+
* Appends a generator to its owning plugin so the generate loop can call it directly.
|
|
1469
1380
|
*
|
|
1470
|
-
* The
|
|
1471
|
-
*
|
|
1381
|
+
* The generator's `schema`, `operation`, and `operations` methods run per node during the AST
|
|
1382
|
+
* walk in `#runGenerators`, and their result is routed through `dispatch`. Because a generator is
|
|
1383
|
+
* bound to a plugin, generators from different plugins never cross-fire without a name check. The
|
|
1384
|
+
* renderer comes from `generator.renderer`; set it to `null` (or leave it unset) to opt out of
|
|
1385
|
+
* rendering.
|
|
1472
1386
|
*
|
|
1473
1387
|
* Call this method inside `addGenerator()` (in `kubb:plugin:setup`) to wire up a generator.
|
|
1474
1388
|
*/
|
|
1475
1389
|
registerGenerator(pluginName, generator) {
|
|
1476
|
-
const
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
if (ctx.plugin.name !== pluginName) return;
|
|
1480
|
-
const result = await method(node, ctx);
|
|
1481
|
-
await this.dispatch({
|
|
1482
|
-
result,
|
|
1483
|
-
renderer: generator.renderer
|
|
1484
|
-
});
|
|
1485
|
-
};
|
|
1486
|
-
};
|
|
1487
|
-
this.#unhooks.push(this.hooks.addHooks({
|
|
1488
|
-
"kubb:generate:schema": wrap(generator.schema),
|
|
1489
|
-
"kubb:generate:operation": wrap(generator.operation),
|
|
1490
|
-
"kubb:generate:operations": wrap(generator.operations)
|
|
1491
|
-
}));
|
|
1492
|
-
this.#hookGeneratorPlugins.add(pluginName);
|
|
1390
|
+
const plugin = this.plugins.get(pluginName);
|
|
1391
|
+
if (!plugin) return;
|
|
1392
|
+
plugin.generators = plugin.generators ? [...plugin.generators, generator] : [generator];
|
|
1493
1393
|
}
|
|
1494
1394
|
/**
|
|
1495
1395
|
* Returns `true` when at least one generator was registered for the given plugin
|
|
1496
1396
|
* via `addGenerator()` in `kubb:plugin:setup`.
|
|
1497
1397
|
*
|
|
1498
|
-
* Used by the build loop to decide whether to walk the AST and
|
|
1398
|
+
* Used by the build loop to decide whether to walk the AST and run the generators
|
|
1499
1399
|
* for a plugin.
|
|
1500
1400
|
*/
|
|
1501
1401
|
hasHookGenerators(pluginName) {
|
|
1502
|
-
return this
|
|
1402
|
+
return (this.plugins.get(pluginName)?.generators?.length ?? 0) > 0;
|
|
1503
1403
|
}
|
|
1504
1404
|
/**
|
|
1505
1405
|
* Runs the full plugin pipeline. Returns the diagnostics collected so far even
|
|
@@ -1510,21 +1410,25 @@ var KubbDriver = class {
|
|
|
1510
1410
|
async run() {
|
|
1511
1411
|
const { hooks, config, fileManager } = this;
|
|
1512
1412
|
const diagnostics = [];
|
|
1513
|
-
const updateBuffer = [];
|
|
1514
1413
|
const parsersMap = /* @__PURE__ */ new Map();
|
|
1515
1414
|
for (const parser of config.parsers) if (parser.extNames) for (const ext of parser.extNames) parsersMap.set(ext, parser);
|
|
1415
|
+
const updateBuffer = [];
|
|
1516
1416
|
const unhookWrites = fileManager.hooks.addHooks({
|
|
1517
1417
|
start: async (files) => {
|
|
1518
1418
|
await hooks.callHook("kubb:files:processing:start", { files });
|
|
1519
1419
|
},
|
|
1520
|
-
update: (
|
|
1521
|
-
updateBuffer.push(
|
|
1420
|
+
update: ({ file, processed, total, percentage }) => {
|
|
1421
|
+
updateBuffer.push({
|
|
1422
|
+
file,
|
|
1423
|
+
processed,
|
|
1424
|
+
total,
|
|
1425
|
+
percentage,
|
|
1426
|
+
config
|
|
1427
|
+
});
|
|
1522
1428
|
},
|
|
1523
1429
|
end: async (files) => {
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
config
|
|
1527
|
-
})) });
|
|
1430
|
+
updateBuffer.sort((a, b) => a.processed - b.processed);
|
|
1431
|
+
await hooks.callHook("kubb:files:processing:update", { files: updateBuffer });
|
|
1528
1432
|
updateBuffer.length = 0;
|
|
1529
1433
|
await hooks.callHook("kubb:files:processing:end", { files });
|
|
1530
1434
|
}
|
|
@@ -1534,12 +1438,16 @@ var KubbDriver = class {
|
|
|
1534
1438
|
const outputRoot = (0, node_path.resolve)(config.root, config.output.path);
|
|
1535
1439
|
await this.#parseInput();
|
|
1536
1440
|
await this.setupHooks();
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1441
|
+
for (const plugin of this.plugins.values()) plugin.options = normalizePluginOptions(plugin.options, plugin.name);
|
|
1442
|
+
if (this.adapter && this.inputNode) {
|
|
1443
|
+
const buildStartContext = this.#withFiles({
|
|
1444
|
+
config,
|
|
1445
|
+
adapter: this.adapter,
|
|
1446
|
+
meta: this.inputNode.meta,
|
|
1447
|
+
getPlugin: this.getPlugin.bind(this)
|
|
1448
|
+
});
|
|
1449
|
+
await hooks.callHook("kubb:build:start", buildStartContext);
|
|
1450
|
+
}
|
|
1543
1451
|
const generatorPlugins = [];
|
|
1544
1452
|
for (const plugin of this.plugins.values()) {
|
|
1545
1453
|
const context = this.getContext(plugin);
|
|
@@ -1584,10 +1492,11 @@ var KubbDriver = class {
|
|
|
1584
1492
|
});
|
|
1585
1493
|
}
|
|
1586
1494
|
diagnostics.push(...await this.#runGenerators(generatorPlugins));
|
|
1587
|
-
await hooks.callHook("kubb:plugins:end",
|
|
1495
|
+
await hooks.callHook("kubb:plugins:end", this.#withFiles({ config }));
|
|
1588
1496
|
await fileManager.write(fileManager.files, {
|
|
1589
1497
|
storage: config.storage,
|
|
1590
|
-
parsers: parsersMap
|
|
1498
|
+
parsers: parsersMap,
|
|
1499
|
+
manifest: this.options.manifest
|
|
1591
1500
|
});
|
|
1592
1501
|
await hooks.callHook("kubb:build:end", {
|
|
1593
1502
|
files: this.fileManager.files,
|
|
@@ -1603,35 +1512,42 @@ var KubbDriver = class {
|
|
|
1603
1512
|
}
|
|
1604
1513
|
});
|
|
1605
1514
|
}
|
|
1606
|
-
|
|
1607
|
-
|
|
1515
|
+
/**
|
|
1516
|
+
* Widens `extra` with the files present at emit time and a bound `upsertFile`, the shape every
|
|
1517
|
+
* file-carrying hook context shares. Building it here in one place keeps the `files` and
|
|
1518
|
+
* `upsertFile` keys from being dropped by a stray spread at the call site.
|
|
1519
|
+
*/
|
|
1520
|
+
#withFiles(extra) {
|
|
1608
1521
|
return {
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
upsertFile: (...files) => driver.fileManager.upsert(...files)
|
|
1522
|
+
...extra,
|
|
1523
|
+
files: this.fileManager.files,
|
|
1524
|
+
upsertFile: (...files) => this.fileManager.upsert(...files)
|
|
1613
1525
|
};
|
|
1614
1526
|
}
|
|
1615
1527
|
#emitPluginEnd({ plugin, duration, success, error }) {
|
|
1616
|
-
return this.hooks.callHook("kubb:plugin:end",
|
|
1528
|
+
return this.hooks.callHook("kubb:plugin:end", this.#withFiles({
|
|
1617
1529
|
plugin,
|
|
1618
1530
|
duration,
|
|
1619
1531
|
success,
|
|
1620
1532
|
...error ? { error } : {},
|
|
1621
1533
|
config: this.config
|
|
1622
|
-
}
|
|
1534
|
+
}));
|
|
1623
1535
|
}
|
|
1624
1536
|
/**
|
|
1625
|
-
* Runs schemas and operations through every plugin's generators.
|
|
1626
|
-
*
|
|
1627
|
-
*
|
|
1628
|
-
* so
|
|
1629
|
-
*
|
|
1630
|
-
*
|
|
1537
|
+
* Runs schemas and operations through every plugin's generators. The walk is node-outer: each
|
|
1538
|
+
* schema is visited once and each operation once, and the node fans out to the matching
|
|
1539
|
+
* generators of every plugin in dependency order. A per-node cache (`createNodeCache`) is created
|
|
1540
|
+
* once per node and shared by all of that node's plugins, so node-derived work is computed once
|
|
1541
|
+
* and reused instead of recomputed per plugin. Each node still runs through the plugin's macros
|
|
1542
|
+
* (from `this.#transforms`) and its exclude/include/override filters before that plugin's
|
|
1543
|
+
* generator sees it, so plugins stay isolated. Schemas run before operations so file output
|
|
1544
|
+
* stays deterministic across runs. A generator with a `match` predicate that resolves `false`
|
|
1545
|
+
* for a node is skipped for that node, without calling `schema`/`operation`.
|
|
1631
1546
|
*
|
|
1632
|
-
*
|
|
1633
|
-
*
|
|
1634
|
-
* `Plugins N/M` counter.
|
|
1547
|
+
* A failing plugin is dropped from the remaining walk, contributes an error diagnostic, and no
|
|
1548
|
+
* longer aborts the other plugins. `kubb:plugin:end` and each plugin's `timing` diagnostic fire
|
|
1549
|
+
* in dependency order once the walk finishes, driving the CLI's `Plugins N/M` counter. The
|
|
1550
|
+
* `operations` batch fires once per plugin after the single operation walk.
|
|
1635
1551
|
*
|
|
1636
1552
|
* When `this.inputNode` is `null`, every entry still gets a `kubb:plugin:end` so
|
|
1637
1553
|
* post-plugin listeners (the barrel writer and friends) complete.
|
|
@@ -1656,13 +1572,10 @@ var KubbDriver = class {
|
|
|
1656
1572
|
}
|
|
1657
1573
|
const transforms = this.#transforms;
|
|
1658
1574
|
const { schemas, operations } = this.inputNode;
|
|
1659
|
-
const emitsSchemaHook = this.hooks.listenerCount("kubb:generate:schema") > 0;
|
|
1660
|
-
const emitsOperationHook = this.hooks.listenerCount("kubb:generate:operation") > 0;
|
|
1661
|
-
const emitsOperationsHook = this.hooks.listenerCount("kubb:generate:operations") > 0;
|
|
1662
1575
|
const allowedSchemaNamesByPlugin = /* @__PURE__ */ new Map();
|
|
1663
1576
|
for (const { plugin } of entries) {
|
|
1664
1577
|
const { exclude, include, override } = plugin.options;
|
|
1665
|
-
if (!((include?.some(({ type }) => OPERATION_FILTER_TYPES.has(type)) ?? false) && !(include?.some(({ type }) => type === "schemaName") ?? false))) continue;
|
|
1578
|
+
if (!((include?.some(({ type }) => require_usingCtx.OPERATION_FILTER_TYPES.has(type)) ?? false) && !(include?.some(({ type }) => type === "schemaName") ?? false))) continue;
|
|
1666
1579
|
const resolver = this.getResolver(plugin.name);
|
|
1667
1580
|
const includedOps = operations.filter((operation) => resolver.default.options(operation, {
|
|
1668
1581
|
options: plugin.options,
|
|
@@ -1672,88 +1585,133 @@ var KubbDriver = class {
|
|
|
1672
1585
|
}) !== null);
|
|
1673
1586
|
allowedSchemaNamesByPlugin.set(plugin.name, (0, _kubb_ast.collectUsedSchemaNames)(includedOps, schemas));
|
|
1674
1587
|
}
|
|
1675
|
-
|
|
1588
|
+
const states = entries.map(({ plugin, context, hrStart }) => {
|
|
1676
1589
|
const generatorContext = {
|
|
1677
1590
|
...context,
|
|
1678
1591
|
resolver: this.getResolver(plugin.name)
|
|
1679
1592
|
};
|
|
1680
1593
|
const { exclude, include, override } = plugin.options;
|
|
1681
|
-
const
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
if (options === null) return null;
|
|
1697
|
-
return {
|
|
1698
|
-
transformedNode,
|
|
1699
|
-
options
|
|
1700
|
-
};
|
|
1594
|
+
const generators = plugin.generators ?? [];
|
|
1595
|
+
return {
|
|
1596
|
+
plugin,
|
|
1597
|
+
hrStart,
|
|
1598
|
+
generatorContext,
|
|
1599
|
+
exclude,
|
|
1600
|
+
include,
|
|
1601
|
+
override,
|
|
1602
|
+
optionsAreStatic: !exclude?.length && !include?.length && !override?.length,
|
|
1603
|
+
allowedSchemaNames: allowedSchemaNamesByPlugin.get(plugin.name) ?? null,
|
|
1604
|
+
schemaGenerators: generators.filter((generator) => generator.schema),
|
|
1605
|
+
operationGenerators: generators.filter((generator) => generator.operation),
|
|
1606
|
+
operationsGenerators: generators.filter((generator) => generator.operations),
|
|
1607
|
+
pluginOperations: [],
|
|
1608
|
+
error: null
|
|
1701
1609
|
};
|
|
1702
|
-
|
|
1703
|
-
|
|
1610
|
+
});
|
|
1611
|
+
const resolveForPlugin = (state, node) => {
|
|
1612
|
+
const transformedNode = transforms.applyTo(state.plugin.name, node);
|
|
1613
|
+
if (state.optionsAreStatic) return {
|
|
1614
|
+
transformedNode,
|
|
1615
|
+
options: state.plugin.options
|
|
1616
|
+
};
|
|
1617
|
+
const options = state.generatorContext.resolver.default.options(transformedNode, {
|
|
1618
|
+
options: state.plugin.options,
|
|
1619
|
+
exclude: state.exclude,
|
|
1620
|
+
include: state.include,
|
|
1621
|
+
override: state.override
|
|
1622
|
+
});
|
|
1623
|
+
if (options === null) return null;
|
|
1624
|
+
return {
|
|
1625
|
+
transformedNode,
|
|
1626
|
+
options
|
|
1627
|
+
};
|
|
1628
|
+
};
|
|
1629
|
+
for (const node of schemas) {
|
|
1630
|
+
const cache = require_usingCtx.createNodeCache();
|
|
1631
|
+
for (const state of states) {
|
|
1632
|
+
if (state.error || !state.schemaGenerators.length) continue;
|
|
1704
1633
|
try {
|
|
1705
|
-
const resolved = resolveForPlugin(node);
|
|
1634
|
+
const resolved = resolveForPlugin(state, node);
|
|
1706
1635
|
if (!resolved) continue;
|
|
1707
1636
|
const { transformedNode, options } = resolved;
|
|
1708
|
-
if (allowedSchemaNames !== null && transformedNode.name && !allowedSchemaNames.has(transformedNode.name)) continue;
|
|
1709
|
-
|
|
1710
|
-
...generatorContext,
|
|
1711
|
-
options
|
|
1712
|
-
|
|
1637
|
+
if (state.allowedSchemaNames !== null && transformedNode.name && !state.allowedSchemaNames.has(transformedNode.name)) continue;
|
|
1638
|
+
const ctx = {
|
|
1639
|
+
...state.generatorContext,
|
|
1640
|
+
options,
|
|
1641
|
+
cache
|
|
1642
|
+
};
|
|
1643
|
+
for (const generator of state.schemaGenerators) {
|
|
1644
|
+
if (!(generator.match ? await generator.match(transformedNode, ctx) : true)) continue;
|
|
1645
|
+
await this.dispatch({
|
|
1646
|
+
result: await generator.schema(transformedNode, ctx),
|
|
1647
|
+
renderer: generator.renderer
|
|
1648
|
+
});
|
|
1649
|
+
}
|
|
1650
|
+
await this.hooks.callHook("kubb:generate:schema", transformedNode, ctx);
|
|
1713
1651
|
} catch (caughtError) {
|
|
1714
|
-
error = require_usingCtx.toError(caughtError);
|
|
1652
|
+
state.error = require_usingCtx.toError(caughtError);
|
|
1715
1653
|
}
|
|
1716
1654
|
}
|
|
1717
|
-
|
|
1718
|
-
|
|
1655
|
+
}
|
|
1656
|
+
for (const node of operations) {
|
|
1657
|
+
const cache = require_usingCtx.createNodeCache();
|
|
1658
|
+
for (const state of states) {
|
|
1659
|
+
if (state.error || !state.operationGenerators.length && !state.operationsGenerators.length) continue;
|
|
1719
1660
|
try {
|
|
1720
|
-
const resolved = resolveForPlugin(node);
|
|
1661
|
+
const resolved = resolveForPlugin(state, node);
|
|
1721
1662
|
if (!resolved) continue;
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1663
|
+
state.pluginOperations.push(resolved.transformedNode);
|
|
1664
|
+
if (state.operationGenerators.length) {
|
|
1665
|
+
const ctx = {
|
|
1666
|
+
...state.generatorContext,
|
|
1667
|
+
options: resolved.options,
|
|
1668
|
+
cache
|
|
1669
|
+
};
|
|
1670
|
+
for (const generator of state.operationGenerators) {
|
|
1671
|
+
if (!(generator.match ? await generator.match(resolved.transformedNode, ctx) : true)) continue;
|
|
1672
|
+
await this.dispatch({
|
|
1673
|
+
result: await generator.operation(resolved.transformedNode, ctx),
|
|
1674
|
+
renderer: generator.renderer
|
|
1675
|
+
});
|
|
1676
|
+
}
|
|
1677
|
+
await this.hooks.callHook("kubb:generate:operation", resolved.transformedNode, ctx);
|
|
1678
|
+
}
|
|
1726
1679
|
} catch (caughtError) {
|
|
1727
|
-
error = require_usingCtx.toError(caughtError);
|
|
1680
|
+
state.error = require_usingCtx.toError(caughtError);
|
|
1728
1681
|
}
|
|
1729
1682
|
}
|
|
1730
|
-
|
|
1683
|
+
}
|
|
1684
|
+
for (const state of states) {
|
|
1685
|
+
if (state.error || !state.operationsGenerators.length) continue;
|
|
1686
|
+
try {
|
|
1731
1687
|
const ctx = {
|
|
1732
|
-
...generatorContext,
|
|
1733
|
-
options: plugin.options
|
|
1688
|
+
...state.generatorContext,
|
|
1689
|
+
options: state.plugin.options,
|
|
1690
|
+
cache: require_usingCtx.createNodeCache()
|
|
1734
1691
|
};
|
|
1735
|
-
const
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
await this.hooks.callHook("kubb:generate:operations", pluginOperations, ctx);
|
|
1692
|
+
for (const generator of state.operationsGenerators) await this.dispatch({
|
|
1693
|
+
result: await generator.operations(state.pluginOperations, ctx),
|
|
1694
|
+
renderer: generator.renderer
|
|
1695
|
+
});
|
|
1696
|
+
await this.hooks.callHook("kubb:generate:operations", state.pluginOperations, ctx);
|
|
1741
1697
|
} catch (caughtError) {
|
|
1742
|
-
error = require_usingCtx.toError(caughtError);
|
|
1698
|
+
state.error = require_usingCtx.toError(caughtError);
|
|
1743
1699
|
}
|
|
1744
|
-
|
|
1700
|
+
}
|
|
1701
|
+
for (const state of states) {
|
|
1702
|
+
const duration = getElapsedMs(state.hrStart);
|
|
1745
1703
|
await this.#emitPluginEnd({
|
|
1746
|
-
plugin,
|
|
1704
|
+
plugin: state.plugin,
|
|
1747
1705
|
duration,
|
|
1748
|
-
success: !error,
|
|
1749
|
-
error: error ?? void 0
|
|
1706
|
+
success: !state.error,
|
|
1707
|
+
error: state.error ?? void 0
|
|
1750
1708
|
});
|
|
1751
|
-
if (error) diagnostics.push({
|
|
1752
|
-
...Diagnostics.from(error),
|
|
1753
|
-
plugin: plugin.name
|
|
1709
|
+
if (state.error) diagnostics.push({
|
|
1710
|
+
...Diagnostics.from(state.error),
|
|
1711
|
+
plugin: state.plugin.name
|
|
1754
1712
|
});
|
|
1755
1713
|
diagnostics.push(Diagnostics.performance({
|
|
1756
|
-
plugin: plugin.name,
|
|
1714
|
+
plugin: state.plugin.name,
|
|
1757
1715
|
duration
|
|
1758
1716
|
}));
|
|
1759
1717
|
}
|
|
@@ -1798,10 +1756,7 @@ var KubbDriver = class {
|
|
|
1798
1756
|
dispose() {
|
|
1799
1757
|
for (const unhook of this.#unhooks) unhook();
|
|
1800
1758
|
this.#unhooks.length = 0;
|
|
1801
|
-
this.#hookGeneratorPlugins.clear();
|
|
1802
1759
|
this.#transforms.dispose();
|
|
1803
|
-
this.#resolvers.clear();
|
|
1804
|
-
this.#defaultResolvers.clear();
|
|
1805
1760
|
this.fileManager.dispose();
|
|
1806
1761
|
this.inputNode = null;
|
|
1807
1762
|
this.#adapterSource = null;
|
|
@@ -1809,21 +1764,17 @@ var KubbDriver = class {
|
|
|
1809
1764
|
[Symbol.dispose]() {
|
|
1810
1765
|
this.dispose();
|
|
1811
1766
|
}
|
|
1812
|
-
#getDefaultResolver = memoize(this.#defaultResolvers, (pluginName) => createResolver({ pluginName }));
|
|
1813
1767
|
/**
|
|
1814
|
-
* Merges `partial`
|
|
1815
|
-
*
|
|
1816
|
-
* get the up-to-date resolver without going through `getResolver()`.
|
|
1768
|
+
* Merges `partial` onto a fresh default resolver and stores the result on `plugin.resolver`,
|
|
1769
|
+
* which is the single source `getResolver` and `getPlugin(name).resolver` both read.
|
|
1817
1770
|
*/
|
|
1818
1771
|
setPluginResolver(pluginName, partial) {
|
|
1819
|
-
const defaultResolver = this.#getDefaultResolver(pluginName);
|
|
1820
|
-
const merged = Resolver.merge(defaultResolver, partial);
|
|
1821
|
-
this.#resolvers.set(pluginName, merged);
|
|
1822
1772
|
const plugin = this.plugins.get(pluginName);
|
|
1823
|
-
if (plugin)
|
|
1773
|
+
if (!plugin) return;
|
|
1774
|
+
plugin.resolver = Resolver.merge(createResolver({ pluginName }), partial);
|
|
1824
1775
|
}
|
|
1825
1776
|
getResolver(pluginName) {
|
|
1826
|
-
return this
|
|
1777
|
+
return this.plugins.get(pluginName)?.resolver ?? createResolver({ pluginName });
|
|
1827
1778
|
}
|
|
1828
1779
|
getContext(plugin) {
|
|
1829
1780
|
const driver = this;
|
|
@@ -1906,6 +1857,72 @@ var KubbDriver = class {
|
|
|
1906
1857
|
}
|
|
1907
1858
|
};
|
|
1908
1859
|
//#endregion
|
|
1860
|
+
//#region src/outputManifest.ts
|
|
1861
|
+
/**
|
|
1862
|
+
* Bumped when the stored shape changes, so an older cache is discarded instead of misread.
|
|
1863
|
+
*/
|
|
1864
|
+
const VERSION = 1;
|
|
1865
|
+
function hash(value) {
|
|
1866
|
+
return (0, node_crypto.createHash)("sha256").update(value).digest("hex");
|
|
1867
|
+
}
|
|
1868
|
+
const MANIFEST_KEY = "output-manifest.json";
|
|
1869
|
+
async function loadEntries({ cache }) {
|
|
1870
|
+
try {
|
|
1871
|
+
const stored = await cache.readItem(MANIFEST_KEY);
|
|
1872
|
+
if (stored === null) return {};
|
|
1873
|
+
const data = JSON.parse(stored);
|
|
1874
|
+
if (data.version !== VERSION) return {};
|
|
1875
|
+
if (typeof data.entries !== "object" || data.entries === null || Array.isArray(data.entries)) return {};
|
|
1876
|
+
return data.entries;
|
|
1877
|
+
} catch {
|
|
1878
|
+
return {};
|
|
1879
|
+
}
|
|
1880
|
+
}
|
|
1881
|
+
/**
|
|
1882
|
+
* Loads the stored manifest, starting empty when it is missing, unreadable, or from an older
|
|
1883
|
+
* version. `storage` holds the generated files, `cache` holds the manifest itself.
|
|
1884
|
+
*
|
|
1885
|
+
* @example
|
|
1886
|
+
* ```ts
|
|
1887
|
+
* const manifest = await createOutputManifest({ storage: config.storage, cache: cacheStorage({ root: config.root }) })
|
|
1888
|
+
* ```
|
|
1889
|
+
*/
|
|
1890
|
+
async function createOutputManifest({ storage, cache }) {
|
|
1891
|
+
const entries = await loadEntries({ cache });
|
|
1892
|
+
const tracked = /* @__PURE__ */ new Map();
|
|
1893
|
+
return {
|
|
1894
|
+
isUpToDate({ key, source, disk }) {
|
|
1895
|
+
const entry = entries[key];
|
|
1896
|
+
if (!entry) return false;
|
|
1897
|
+
return entry.source === hash(source) && entry.output === hash(disk);
|
|
1898
|
+
},
|
|
1899
|
+
track({ key, source }) {
|
|
1900
|
+
tracked.set(key, hash(source));
|
|
1901
|
+
},
|
|
1902
|
+
async commit() {
|
|
1903
|
+
try {
|
|
1904
|
+
const next = { ...entries };
|
|
1905
|
+
await require_usingCtx.inParallel({
|
|
1906
|
+
items: [...tracked],
|
|
1907
|
+
limit: 50,
|
|
1908
|
+
run: async ([key, source]) => {
|
|
1909
|
+
const stored = await storage.readItem(key);
|
|
1910
|
+
if (stored === null) return;
|
|
1911
|
+
next[key] = {
|
|
1912
|
+
source,
|
|
1913
|
+
output: hash(stored)
|
|
1914
|
+
};
|
|
1915
|
+
}
|
|
1916
|
+
});
|
|
1917
|
+
await cache.writeItem(MANIFEST_KEY, JSON.stringify({
|
|
1918
|
+
version: VERSION,
|
|
1919
|
+
entries: next
|
|
1920
|
+
}));
|
|
1921
|
+
} catch {}
|
|
1922
|
+
}
|
|
1923
|
+
};
|
|
1924
|
+
}
|
|
1925
|
+
//#endregion
|
|
1909
1926
|
//#region src/createStorage.ts
|
|
1910
1927
|
/**
|
|
1911
1928
|
* Defines a custom storage backend. The builder receives user options and
|
|
@@ -1922,23 +1939,23 @@ var KubbDriver = class {
|
|
|
1922
1939
|
*
|
|
1923
1940
|
* return {
|
|
1924
1941
|
* name: 'memory',
|
|
1925
|
-
* async
|
|
1942
|
+
* async existsItem(key) {
|
|
1926
1943
|
* return store.has(key)
|
|
1927
1944
|
* },
|
|
1928
|
-
* async
|
|
1945
|
+
* async readItem(key) {
|
|
1929
1946
|
* return store.get(key) ?? null
|
|
1930
1947
|
* },
|
|
1931
|
-
* async
|
|
1948
|
+
* async writeItem(key, value) {
|
|
1932
1949
|
* store.set(key, value)
|
|
1933
1950
|
* },
|
|
1934
1951
|
* async removeItem(key) {
|
|
1935
1952
|
* store.delete(key)
|
|
1936
1953
|
* },
|
|
1937
|
-
* async
|
|
1954
|
+
* async readKeys(base) {
|
|
1938
1955
|
* const keys = [...store.keys()]
|
|
1939
1956
|
* return base ? keys.filter((k) => k.startsWith(base)) : keys
|
|
1940
1957
|
* },
|
|
1941
|
-
* async
|
|
1958
|
+
* async empty(base) {
|
|
1942
1959
|
* if (!base) store.clear()
|
|
1943
1960
|
* },
|
|
1944
1961
|
* }
|
|
@@ -1982,10 +1999,11 @@ function createLimiter(concurrency) {
|
|
|
1982
1999
|
*
|
|
1983
2000
|
* Writes are deduplicated and directory-safe:
|
|
1984
2001
|
* - leading and trailing whitespace is trimmed before writing
|
|
1985
|
-
* - the write is skipped when the file content
|
|
2002
|
+
* - the write is skipped when the file already holds that content, ignoring any trailing newline
|
|
2003
|
+
* a formatter left behind
|
|
1986
2004
|
* - missing parent directories are created automatically
|
|
1987
2005
|
* - Bun's native file API is used when running under Bun
|
|
1988
|
-
* - concurrent `
|
|
2006
|
+
* - concurrent `writeItem` calls are capped at {@link WRITE_CONCURRENCY} in flight, so a caller
|
|
1989
2007
|
* can fire every file's write without pacing itself
|
|
1990
2008
|
*
|
|
1991
2009
|
* @example
|
|
@@ -2004,7 +2022,7 @@ const fsStorage = createStorage(() => {
|
|
|
2004
2022
|
const limit = createLimiter(WRITE_CONCURRENCY);
|
|
2005
2023
|
return {
|
|
2006
2024
|
name: "fs",
|
|
2007
|
-
async
|
|
2025
|
+
async existsItem(key) {
|
|
2008
2026
|
try {
|
|
2009
2027
|
await (0, node_fs_promises.access)((0, node_path.resolve)(key));
|
|
2010
2028
|
return true;
|
|
@@ -2012,20 +2030,20 @@ const fsStorage = createStorage(() => {
|
|
|
2012
2030
|
return false;
|
|
2013
2031
|
}
|
|
2014
2032
|
},
|
|
2015
|
-
async
|
|
2033
|
+
async readItem(key) {
|
|
2016
2034
|
try {
|
|
2017
2035
|
return await (0, node_fs_promises.readFile)((0, node_path.resolve)(key), "utf8");
|
|
2018
2036
|
} catch (_error) {
|
|
2019
2037
|
return null;
|
|
2020
2038
|
}
|
|
2021
2039
|
},
|
|
2022
|
-
async
|
|
2040
|
+
async writeItem(key, value) {
|
|
2023
2041
|
await limit(() => require_usingCtx.write((0, node_path.resolve)(key), value, { sanity: false }));
|
|
2024
2042
|
},
|
|
2025
2043
|
async removeItem(key) {
|
|
2026
2044
|
await (0, node_fs_promises.rm)((0, node_path.resolve)(key), { force: true });
|
|
2027
2045
|
},
|
|
2028
|
-
async
|
|
2046
|
+
async readKeys(base) {
|
|
2029
2047
|
const resolvedBase = (0, node_path.resolve)(base ?? process.cwd());
|
|
2030
2048
|
const keys = [];
|
|
2031
2049
|
try {
|
|
@@ -2036,13 +2054,67 @@ const fsStorage = createStorage(() => {
|
|
|
2036
2054
|
} catch (_error) {}
|
|
2037
2055
|
return keys;
|
|
2038
2056
|
},
|
|
2039
|
-
async
|
|
2057
|
+
async empty(base) {
|
|
2040
2058
|
if (!base) return;
|
|
2041
2059
|
await require_usingCtx.clean((0, node_path.resolve)(base));
|
|
2042
2060
|
}
|
|
2043
2061
|
};
|
|
2044
2062
|
});
|
|
2045
2063
|
//#endregion
|
|
2064
|
+
//#region src/storages/cacheStorage.ts
|
|
2065
|
+
/**
|
|
2066
|
+
* Directory Kubb keeps build caches in. A project with a `node_modules` gets
|
|
2067
|
+
* `node_modules/.cache/kubb`, the convention babel and eslint already use, so the cache stays out
|
|
2068
|
+
* of version control. Without one it falls back to the OS temp directory, keyed by root so two
|
|
2069
|
+
* projects sharing that directory keep their own cache.
|
|
2070
|
+
*
|
|
2071
|
+
* @example Inside a project
|
|
2072
|
+
* `resolveCacheDir('/project') // '/project/node_modules/.cache/kubb'`
|
|
2073
|
+
*/
|
|
2074
|
+
function resolveCacheDir(root) {
|
|
2075
|
+
const nodeModules = (0, node_path.join)(root, "node_modules");
|
|
2076
|
+
if ((0, node_fs.existsSync)(nodeModules)) return (0, node_path.join)(nodeModules, ".cache", "kubb");
|
|
2077
|
+
return (0, node_path.join)((0, node_os.tmpdir)(), "kubb", (0, node_crypto.createHash)("sha256").update(root).digest("hex").slice(0, 16));
|
|
2078
|
+
}
|
|
2079
|
+
/**
|
|
2080
|
+
* Filesystem storage for build caches rather than generated code. Keys are plain names resolved
|
|
2081
|
+
* inside {@link resolveCacheDir}, so a caller stores `'x.json'` without knowing where the cache
|
|
2082
|
+
* lives. Kept apart from the configured output storage, which may not be a local disk at all.
|
|
2083
|
+
*
|
|
2084
|
+
* @example
|
|
2085
|
+
* ```ts
|
|
2086
|
+
* const cache = cacheStorage({ root: config.root })
|
|
2087
|
+
* await cache.writeItem('output-manifest.json', JSON.stringify(entries))
|
|
2088
|
+
* ```
|
|
2089
|
+
*/
|
|
2090
|
+
const cacheStorage = createStorage(({ root = process.cwd() }) => {
|
|
2091
|
+
const dir = resolveCacheDir(root);
|
|
2092
|
+
const storage = fsStorage();
|
|
2093
|
+
const toPath = (key) => (0, node_path.join)(dir, key);
|
|
2094
|
+
return {
|
|
2095
|
+
name: "cache",
|
|
2096
|
+
async existsItem(key) {
|
|
2097
|
+
return storage.existsItem(toPath(key));
|
|
2098
|
+
},
|
|
2099
|
+
async readItem(key) {
|
|
2100
|
+
return storage.readItem(toPath(key));
|
|
2101
|
+
},
|
|
2102
|
+
async writeItem(key, value) {
|
|
2103
|
+
return storage.writeItem(toPath(key), value);
|
|
2104
|
+
},
|
|
2105
|
+
async removeItem(key) {
|
|
2106
|
+
return storage.removeItem(toPath(key));
|
|
2107
|
+
},
|
|
2108
|
+
async readKeys(base) {
|
|
2109
|
+
return storage.readKeys(base ? toPath(base) : dir);
|
|
2110
|
+
},
|
|
2111
|
+
async empty(base) {
|
|
2112
|
+
if (!base) return;
|
|
2113
|
+
return storage.empty(toPath(base));
|
|
2114
|
+
}
|
|
2115
|
+
};
|
|
2116
|
+
});
|
|
2117
|
+
//#endregion
|
|
2046
2118
|
//#region src/createKubb.ts
|
|
2047
2119
|
function resolveConfig(userConfig) {
|
|
2048
2120
|
return {
|
|
@@ -2061,6 +2133,13 @@ function resolveConfig(userConfig) {
|
|
|
2061
2133
|
};
|
|
2062
2134
|
}
|
|
2063
2135
|
/**
|
|
2136
|
+
* Whether anything runs over the output directory after the files are written. Only then can the
|
|
2137
|
+
* bytes on disk stop matching what Kubb wrote, which is what the manifest exists to track.
|
|
2138
|
+
*/
|
|
2139
|
+
function hasOutputPasses(output) {
|
|
2140
|
+
return Boolean(output.format || output.lint || output.postGenerate?.length);
|
|
2141
|
+
}
|
|
2142
|
+
/**
|
|
2064
2143
|
* Kubb code-generation instance bound to a single config entry. Resolves the user
|
|
2065
2144
|
* config in the constructor, so `config` is available right away, and shares `hooks`,
|
|
2066
2145
|
* `storage`, and `driver` across the `setup → build` lifecycle.
|
|
@@ -2082,6 +2161,7 @@ var Kubb = class {
|
|
|
2082
2161
|
config;
|
|
2083
2162
|
#driver = null;
|
|
2084
2163
|
#storage = null;
|
|
2164
|
+
#manifest = null;
|
|
2085
2165
|
constructor(userConfig, options = {}) {
|
|
2086
2166
|
this.config = resolveConfig(userConfig);
|
|
2087
2167
|
this.hooks = options.hooks ?? new require_usingCtx.Hookable();
|
|
@@ -2099,7 +2179,14 @@ var Kubb = class {
|
|
|
2099
2179
|
*/
|
|
2100
2180
|
async setup() {
|
|
2101
2181
|
const config = this.config;
|
|
2102
|
-
const
|
|
2182
|
+
const manifest = hasOutputPasses(config.output) ? await createOutputManifest({
|
|
2183
|
+
storage: config.storage,
|
|
2184
|
+
cache: cacheStorage({ root: config.root })
|
|
2185
|
+
}) : void 0;
|
|
2186
|
+
const driver = new KubbDriver(config, {
|
|
2187
|
+
hooks: this.hooks,
|
|
2188
|
+
manifest
|
|
2189
|
+
});
|
|
2103
2190
|
this.hooks.setMaxListeners(Math.max(10, config.plugins.length * 4));
|
|
2104
2191
|
if (config.output.clean) {
|
|
2105
2192
|
const cleanPath = (0, node_path.resolve)(config.root, config.output.path);
|
|
@@ -2110,11 +2197,12 @@ var Kubb = class {
|
|
|
2110
2197
|
help: "Point `output.path` at a subdirectory such as `./src/gen` so clean only removes generated code.",
|
|
2111
2198
|
location: { kind: "config" }
|
|
2112
2199
|
});
|
|
2113
|
-
await config.storage.
|
|
2200
|
+
await config.storage.empty(cleanPath);
|
|
2114
2201
|
}
|
|
2115
2202
|
await driver.setup();
|
|
2116
2203
|
this.#driver = driver;
|
|
2117
2204
|
this.#storage = config.storage;
|
|
2205
|
+
this.#manifest = manifest ?? null;
|
|
2118
2206
|
}
|
|
2119
2207
|
/**
|
|
2120
2208
|
* Runs the full pipeline and throws on any plugin error.
|
|
@@ -2153,6 +2241,70 @@ var Kubb = class {
|
|
|
2153
2241
|
_usingCtx$1.d();
|
|
2154
2242
|
}
|
|
2155
2243
|
}
|
|
2244
|
+
/**
|
|
2245
|
+
* Run one build and its output passes end to end, emitting the surrounding `kubb:generation:*`
|
|
2246
|
+
* hooks. Never throws on a build error: the outcome comes back in {@link GenerateResult} so the
|
|
2247
|
+
* host decides how failures surface. Telemetry and progress narration stay with the host, which
|
|
2248
|
+
* reads the result and subscribes to the `kubb:*` hooks.
|
|
2249
|
+
*
|
|
2250
|
+
* @example
|
|
2251
|
+
* ```ts
|
|
2252
|
+
* const result = await createKubb(config, { hooks }).generate()
|
|
2253
|
+
* if (!result.success) process.exitCode = 1
|
|
2254
|
+
* ```
|
|
2255
|
+
*/
|
|
2256
|
+
async generate(options = {}) {
|
|
2257
|
+
const { hooks, config } = this;
|
|
2258
|
+
const hrStart = process.hrtime();
|
|
2259
|
+
await hooks.callHook("kubb:generation:start", { config });
|
|
2260
|
+
await hooks.callHook("kubb:setup:start");
|
|
2261
|
+
await this.setup();
|
|
2262
|
+
await hooks.callHook("kubb:setup:end");
|
|
2263
|
+
const { files, diagnostics, storage } = await this.safeBuild();
|
|
2264
|
+
for (const diagnostic of diagnostics) {
|
|
2265
|
+
if (!Diagnostics.isProblem(diagnostic)) continue;
|
|
2266
|
+
if (diagnostic.code === Diagnostics.code.unknown) {
|
|
2267
|
+
await hooks.callHook("kubb:error", { error: diagnostic.cause ?? new Error(diagnostic.message) });
|
|
2268
|
+
continue;
|
|
2269
|
+
}
|
|
2270
|
+
await Diagnostics.emit(hooks, diagnostic);
|
|
2271
|
+
}
|
|
2272
|
+
if (Diagnostics.hasError(diagnostics)) {
|
|
2273
|
+
await hooks.callHook("kubb:generation:end", {
|
|
2274
|
+
config,
|
|
2275
|
+
storage,
|
|
2276
|
+
diagnostics,
|
|
2277
|
+
filesCreated: files.length,
|
|
2278
|
+
status: "failed",
|
|
2279
|
+
hrStart
|
|
2280
|
+
});
|
|
2281
|
+
return {
|
|
2282
|
+
success: false,
|
|
2283
|
+
files,
|
|
2284
|
+
diagnostics
|
|
2285
|
+
};
|
|
2286
|
+
}
|
|
2287
|
+
const outputDiagnostics = options.processOutput ? await options.processOutput({
|
|
2288
|
+
config,
|
|
2289
|
+
outputPath: (0, node_path.resolve)(config.root, config.output.path)
|
|
2290
|
+
}) : [];
|
|
2291
|
+
const finalDiagnostics = [...diagnostics, ...outputDiagnostics];
|
|
2292
|
+
const failed = Diagnostics.hasError(outputDiagnostics);
|
|
2293
|
+
if (!failed) await this.#manifest?.commit();
|
|
2294
|
+
await hooks.callHook("kubb:generation:end", {
|
|
2295
|
+
config,
|
|
2296
|
+
storage,
|
|
2297
|
+
diagnostics: finalDiagnostics,
|
|
2298
|
+
filesCreated: files.length,
|
|
2299
|
+
status: failed ? "failed" : "success",
|
|
2300
|
+
hrStart
|
|
2301
|
+
});
|
|
2302
|
+
return {
|
|
2303
|
+
success: !failed,
|
|
2304
|
+
files,
|
|
2305
|
+
diagnostics: finalDiagnostics
|
|
2306
|
+
};
|
|
2307
|
+
}
|
|
2156
2308
|
dispose() {
|
|
2157
2309
|
this.#driver?.dispose();
|
|
2158
2310
|
}
|
|
@@ -2393,11 +2545,13 @@ const fileReporter = createReporter({
|
|
|
2393
2545
|
const { diagnostics, config } = result;
|
|
2394
2546
|
if (diagnostics.length === 0) return;
|
|
2395
2547
|
const report = buildReport(result);
|
|
2396
|
-
const
|
|
2548
|
+
const header = config.name ? `# ${config.name} — ${(/* @__PURE__ */ new Date()).toISOString()}` : `# ${(/* @__PURE__ */ new Date()).toISOString()}`;
|
|
2549
|
+
const sections = [
|
|
2397
2550
|
buildSummarySection(report),
|
|
2398
2551
|
buildProblemSection(diagnostics),
|
|
2399
2552
|
buildTimingSection(report)
|
|
2400
|
-
].filter((section) => section.length > 0)
|
|
2553
|
+
].filter((section) => section.length > 0);
|
|
2554
|
+
const content = (0, node_util.stripVTControlCharacters)([header, ...sections.map((section) => section.join("\n"))].join("\n\n"));
|
|
2401
2555
|
const baseName = `${[
|
|
2402
2556
|
"kubb",
|
|
2403
2557
|
config.name,
|
|
@@ -2550,23 +2704,23 @@ const memoryStorage = createStorage(() => {
|
|
|
2550
2704
|
const store = /* @__PURE__ */ new Map();
|
|
2551
2705
|
return {
|
|
2552
2706
|
name: "memory",
|
|
2553
|
-
async
|
|
2707
|
+
async existsItem(key) {
|
|
2554
2708
|
return store.has(key);
|
|
2555
2709
|
},
|
|
2556
|
-
async
|
|
2710
|
+
async readItem(key) {
|
|
2557
2711
|
return store.get(key) ?? null;
|
|
2558
2712
|
},
|
|
2559
|
-
async
|
|
2713
|
+
async writeItem(key, value) {
|
|
2560
2714
|
store.set(key, value);
|
|
2561
2715
|
},
|
|
2562
2716
|
async removeItem(key) {
|
|
2563
2717
|
store.delete(key);
|
|
2564
2718
|
},
|
|
2565
|
-
async
|
|
2719
|
+
async readKeys(base) {
|
|
2566
2720
|
const keys = [...store.keys()];
|
|
2567
2721
|
return base ? keys.filter((k) => k.startsWith(base)) : keys;
|
|
2568
2722
|
},
|
|
2569
|
-
async
|
|
2723
|
+
async empty(base) {
|
|
2570
2724
|
if (!base) {
|
|
2571
2725
|
store.clear();
|
|
2572
2726
|
return;
|