@kubb/core 5.0.0-beta.36 → 5.0.0-beta.38
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/{KubbDriver-Dil5m3NF.cjs → KubbDriver-BYBUfOZ8.cjs} +1081 -412
- package/dist/KubbDriver-BYBUfOZ8.cjs.map +1 -0
- package/dist/{KubbDriver-DrG5-FFe.js → KubbDriver-CyNF-NIb.js} +1040 -401
- package/dist/KubbDriver-CyNF-NIb.js.map +1 -0
- package/dist/{createKubb-BKpcUB6g.d.ts → diagnostics-CYfKPtbU.d.ts} +805 -285
- package/dist/index.cjs +66 -89
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +58 -90
- package/dist/index.js.map +1 -1
- package/dist/mocks.cjs +47 -24
- package/dist/mocks.cjs.map +1 -1
- package/dist/mocks.d.ts +2 -2
- package/dist/mocks.js +47 -24
- package/dist/mocks.js.map +1 -1
- package/package.json +4 -4
- package/src/FileManager.ts +23 -18
- package/src/FileProcessor.ts +142 -24
- package/src/HookRegistry.ts +45 -0
- package/src/KubbDriver.ts +327 -319
- package/src/Transform.ts +58 -0
- package/src/constants.ts +105 -11
- package/src/createKubb.ts +95 -201
- package/src/createRenderer.ts +2 -2
- package/src/createReporter.ts +117 -0
- package/src/createStorage.ts +1 -1
- package/src/defineGenerator.ts +11 -7
- package/src/defineParser.ts +1 -1
- package/src/definePlugin.ts +10 -22
- package/src/defineResolver.ts +29 -24
- package/src/devtools.ts +2 -3
- package/src/diagnostics.ts +591 -0
- package/src/index.ts +11 -1
- package/src/mocks.ts +28 -7
- package/src/types.ts +16 -4
- package/dist/KubbDriver-Dil5m3NF.cjs.map +0 -1
- package/dist/KubbDriver-DrG5-FFe.js.map +0 -1
|
@@ -28,6 +28,7 @@ let node_events = require("node:events");
|
|
|
28
28
|
let node_path = require("node:path");
|
|
29
29
|
node_path = __toESM(node_path, 1);
|
|
30
30
|
let _kubb_ast = require("@kubb/ast");
|
|
31
|
+
let node_async_hooks = require("node:async_hooks");
|
|
31
32
|
let fflate = require("fflate");
|
|
32
33
|
let tinyexec = require("tinyexec");
|
|
33
34
|
//#region ../../internals/utils/src/errors.ts
|
|
@@ -62,6 +63,18 @@ var BuildError = class extends Error {
|
|
|
62
63
|
function toError(value) {
|
|
63
64
|
return value instanceof Error ? value : new Error(String(value));
|
|
64
65
|
}
|
|
66
|
+
/**
|
|
67
|
+
* Extracts a human-readable message from any thrown value.
|
|
68
|
+
*
|
|
69
|
+
* @example
|
|
70
|
+
* ```ts
|
|
71
|
+
* getErrorMessage(new Error('oops')) // 'oops'
|
|
72
|
+
* getErrorMessage('plain string') // 'plain string'
|
|
73
|
+
* ```
|
|
74
|
+
*/
|
|
75
|
+
function getErrorMessage(value) {
|
|
76
|
+
return value instanceof Error ? value.message : String(value);
|
|
77
|
+
}
|
|
65
78
|
//#endregion
|
|
66
79
|
//#region ../../internals/utils/src/asyncEventEmitter.ts
|
|
67
80
|
/**
|
|
@@ -161,6 +174,24 @@ var AsyncEventEmitter = class {
|
|
|
161
174
|
return this.#emitter.listenerCount(eventName);
|
|
162
175
|
}
|
|
163
176
|
/**
|
|
177
|
+
* Raises or lowers the per-event listener ceiling before Node warns about a memory leak.
|
|
178
|
+
* Set this above the expected listener count when many listeners attach by design.
|
|
179
|
+
*
|
|
180
|
+
* @example
|
|
181
|
+
* ```ts
|
|
182
|
+
* emitter.setMaxListeners(40)
|
|
183
|
+
* ```
|
|
184
|
+
*/
|
|
185
|
+
setMaxListeners(max) {
|
|
186
|
+
this.#emitter.setMaxListeners(max);
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* Returns the current per-event listener ceiling.
|
|
190
|
+
*/
|
|
191
|
+
getMaxListeners() {
|
|
192
|
+
return this.#emitter.getMaxListeners();
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
164
195
|
* Removes all listeners from every event channel.
|
|
165
196
|
*
|
|
166
197
|
* @example
|
|
@@ -254,21 +285,6 @@ function getElapsedMs(hrStart) {
|
|
|
254
285
|
const ms = seconds * 1e3 + nanoseconds / 1e6;
|
|
255
286
|
return Math.round(ms * 100) / 100;
|
|
256
287
|
}
|
|
257
|
-
/**
|
|
258
|
-
* Converts a millisecond duration into a human-readable string (`ms`, `s`, or `m s`).
|
|
259
|
-
*
|
|
260
|
-
* @example
|
|
261
|
-
* ```ts
|
|
262
|
-
* formatMs(250) // '250ms'
|
|
263
|
-
* formatMs(1500) // '1.50s'
|
|
264
|
-
* formatMs(90000) // '1m 30.0s'
|
|
265
|
-
* ```
|
|
266
|
-
*/
|
|
267
|
-
function formatMs(ms) {
|
|
268
|
-
if (ms >= 6e4) return `${Math.floor(ms / 6e4)}m ${(ms % 6e4 / 1e3).toFixed(1)}s`;
|
|
269
|
-
if (ms >= 1e3) return `${(ms / 1e3).toFixed(2)}s`;
|
|
270
|
-
return `${Math.round(ms)}ms`;
|
|
271
|
-
}
|
|
272
288
|
//#endregion
|
|
273
289
|
//#region ../../internals/utils/src/promise.ts
|
|
274
290
|
function* chunks(arr, size) {
|
|
@@ -636,13 +652,17 @@ var URLPath = class {
|
|
|
636
652
|
*/
|
|
637
653
|
const DEFAULT_STUDIO_URL = "https://kubb.studio";
|
|
638
654
|
/**
|
|
639
|
-
*
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
/**
|
|
643
|
-
* Default file-extension mapping used when no explicit mapping is configured.
|
|
655
|
+
* Plugin `include` filter types that select operations directly. When one of these is set
|
|
656
|
+
* without a `schemaName` include, the generate phase pre-scans operations to compute the set
|
|
657
|
+
* of schemas they reach, so unreachable schemas can be pruned for that plugin.
|
|
644
658
|
*/
|
|
645
|
-
const
|
|
659
|
+
const OPERATION_FILTER_TYPES = new Set([
|
|
660
|
+
"tag",
|
|
661
|
+
"operationId",
|
|
662
|
+
"path",
|
|
663
|
+
"method",
|
|
664
|
+
"contentType"
|
|
665
|
+
]);
|
|
646
666
|
/**
|
|
647
667
|
* Numeric log-level thresholds used internally to compare verbosity.
|
|
648
668
|
*
|
|
@@ -653,8 +673,453 @@ const logLevel = {
|
|
|
653
673
|
error: 0,
|
|
654
674
|
warn: 1,
|
|
655
675
|
info: 3,
|
|
656
|
-
verbose: 4
|
|
657
|
-
|
|
676
|
+
verbose: 4
|
|
677
|
+
};
|
|
678
|
+
/**
|
|
679
|
+
* Stable codes Kubb attaches to a `Diagnostic`. Each maps to a known failure mode
|
|
680
|
+
* and stays stable so it can be referenced in tooling and (later) docs. Reference
|
|
681
|
+
* these instead of inlining the string at a throw site.
|
|
682
|
+
*/
|
|
683
|
+
const diagnosticCode = {
|
|
684
|
+
/**
|
|
685
|
+
* Fallback for an unstructured error with no specific code.
|
|
686
|
+
*/
|
|
687
|
+
unknown: "KUBB_UNKNOWN",
|
|
688
|
+
/**
|
|
689
|
+
* The `input.path` file or URL could not be read.
|
|
690
|
+
*/
|
|
691
|
+
inputNotFound: "KUBB_INPUT_NOT_FOUND",
|
|
692
|
+
/**
|
|
693
|
+
* An adapter was configured without an `input`.
|
|
694
|
+
*/
|
|
695
|
+
inputRequired: "KUBB_INPUT_REQUIRED",
|
|
696
|
+
/**
|
|
697
|
+
* A `$ref` (or equivalent reference) could not be resolved in the source document.
|
|
698
|
+
*/
|
|
699
|
+
refNotFound: "KUBB_REF_NOT_FOUND",
|
|
700
|
+
/**
|
|
701
|
+
* A server variable value is not allowed by its `enum`.
|
|
702
|
+
*/
|
|
703
|
+
invalidServerVariable: "KUBB_INVALID_SERVER_VARIABLE",
|
|
704
|
+
/**
|
|
705
|
+
* A required plugin is missing from the config.
|
|
706
|
+
*/
|
|
707
|
+
pluginNotFound: "KUBB_PLUGIN_NOT_FOUND",
|
|
708
|
+
/**
|
|
709
|
+
* A plugin threw while generating.
|
|
710
|
+
*/
|
|
711
|
+
pluginFailed: "KUBB_PLUGIN_FAILED",
|
|
712
|
+
/**
|
|
713
|
+
* A plugin reported a non-fatal warning through `ctx.warn`.
|
|
714
|
+
*/
|
|
715
|
+
pluginWarning: "KUBB_PLUGIN_WARNING",
|
|
716
|
+
/**
|
|
717
|
+
* A plugin reported an informational message through `ctx.info`.
|
|
718
|
+
*/
|
|
719
|
+
pluginInfo: "KUBB_PLUGIN_INFO",
|
|
720
|
+
/**
|
|
721
|
+
* A schema uses a `format` Kubb does not map to a specific type. Reserved for
|
|
722
|
+
* adapters to emit as a `warning`.
|
|
723
|
+
*/
|
|
724
|
+
unsupportedFormat: "KUBB_UNSUPPORTED_FORMAT",
|
|
725
|
+
/**
|
|
726
|
+
* A referenced schema or operation is marked `deprecated`. Reserved for adapters
|
|
727
|
+
* to emit as an `info`.
|
|
728
|
+
*/
|
|
729
|
+
deprecated: "KUBB_DEPRECATED",
|
|
730
|
+
/**
|
|
731
|
+
* An adapter is required but the config has none. The build cannot read the input
|
|
732
|
+
* without one.
|
|
733
|
+
*/
|
|
734
|
+
adapterRequired: "KUBB_ADAPTER_REQUIRED",
|
|
735
|
+
/**
|
|
736
|
+
* The `devtools` config is set to something other than an object.
|
|
737
|
+
*/
|
|
738
|
+
devtoolsInvalid: "KUBB_DEVTOOLS_INVALID",
|
|
739
|
+
/**
|
|
740
|
+
* A resolved output path escapes the output directory, which can stem from a path
|
|
741
|
+
* traversal in the spec or a misconfigured `group.name`.
|
|
742
|
+
*/
|
|
743
|
+
pathTraversal: "KUBB_PATH_TRAVERSAL",
|
|
744
|
+
/**
|
|
745
|
+
* A post-generate shell hook (`hooks.done`) exited with a failure.
|
|
746
|
+
*/
|
|
747
|
+
hookFailed: "KUBB_HOOK_FAILED",
|
|
748
|
+
/**
|
|
749
|
+
* The formatter pass over the generated files failed.
|
|
750
|
+
*/
|
|
751
|
+
formatFailed: "KUBB_FORMAT_FAILED",
|
|
752
|
+
/**
|
|
753
|
+
* The linter pass over the generated files failed.
|
|
754
|
+
*/
|
|
755
|
+
lintFailed: "KUBB_LINT_FAILED",
|
|
756
|
+
/**
|
|
757
|
+
* Not a failure. Carries a plugin's elapsed time, summed into the run total.
|
|
758
|
+
*/
|
|
759
|
+
performance: "KUBB_PERFORMANCE",
|
|
760
|
+
/**
|
|
761
|
+
* Not a failure. A newer Kubb version is available on npm.
|
|
762
|
+
*/
|
|
763
|
+
updateAvailable: "KUBB_UPDATE_AVAILABLE"
|
|
764
|
+
};
|
|
765
|
+
//#endregion
|
|
766
|
+
//#region src/diagnostics.ts
|
|
767
|
+
/**
|
|
768
|
+
* Docs major, derived from the package version so the link tracks the published major.
|
|
769
|
+
*/
|
|
770
|
+
const docsMajor = "5.0.0-beta.38".split(".")[0] ?? "5";
|
|
771
|
+
/**
|
|
772
|
+
* Narrows a {@link Diagnostic} to the variant for `code`, or `null` when it does not match.
|
|
773
|
+
*
|
|
774
|
+
* @example
|
|
775
|
+
* ```ts
|
|
776
|
+
* const update = narrowDiagnostic(diagnostic, diagnosticCode.updateAvailable)
|
|
777
|
+
* if (update) {
|
|
778
|
+
* console.log(update.latestVersion)
|
|
779
|
+
* }
|
|
780
|
+
* ```
|
|
781
|
+
*/
|
|
782
|
+
function narrowDiagnostic(diagnostic, code) {
|
|
783
|
+
return diagnostic.code === code ? diagnostic : null;
|
|
784
|
+
}
|
|
785
|
+
/**
|
|
786
|
+
* Builds a type guard that narrows a {@link Diagnostic} to the variant for `kind`. A diagnostic
|
|
787
|
+
* with no `kind` is treated as a `problem`.
|
|
788
|
+
*/
|
|
789
|
+
function isKind(kind) {
|
|
790
|
+
return (diagnostic) => (diagnostic.kind ?? "problem") === kind;
|
|
791
|
+
}
|
|
792
|
+
/**
|
|
793
|
+
* Returns `true` when the diagnostic is a build {@link ProblemDiagnostic}.
|
|
794
|
+
*
|
|
795
|
+
* @example
|
|
796
|
+
* ```ts
|
|
797
|
+
* if (isProblemDiagnostic(diagnostic)) {
|
|
798
|
+
* console.log(diagnostic.location)
|
|
799
|
+
* }
|
|
800
|
+
* ```
|
|
801
|
+
*/
|
|
802
|
+
const isProblemDiagnostic = isKind("problem");
|
|
803
|
+
/**
|
|
804
|
+
* Returns `true` when the diagnostic is a per-plugin {@link PerformanceDiagnostic}.
|
|
805
|
+
*
|
|
806
|
+
* @example
|
|
807
|
+
* ```ts
|
|
808
|
+
* const timings = diagnostics.filter(isPerformanceDiagnostic)
|
|
809
|
+
* ```
|
|
810
|
+
*/
|
|
811
|
+
const isPerformanceDiagnostic = isKind("performance");
|
|
812
|
+
/**
|
|
813
|
+
* Returns `true` when the diagnostic is a version-update {@link UpdateDiagnostic}.
|
|
814
|
+
*
|
|
815
|
+
* @example
|
|
816
|
+
* ```ts
|
|
817
|
+
* if (isUpdateDiagnostic(diagnostic)) {
|
|
818
|
+
* console.log(diagnostic.latestVersion)
|
|
819
|
+
* }
|
|
820
|
+
* ```
|
|
821
|
+
*/
|
|
822
|
+
const isUpdateDiagnostic = isKind("update");
|
|
823
|
+
/**
|
|
824
|
+
* An `Error` that carries a {@link Diagnostic}, so structured problems can flow
|
|
825
|
+
* through the existing throw/catch paths while keeping their code and location.
|
|
826
|
+
*
|
|
827
|
+
* @example
|
|
828
|
+
* ```ts
|
|
829
|
+
* throw new DiagnosticError({ code: diagnosticCode.refNotFound, severity: 'error', message: `Could not find ${ref}`, location: { kind: 'schema', pointer: ref, ref } })
|
|
830
|
+
* ```
|
|
831
|
+
*/
|
|
832
|
+
var DiagnosticError = class extends Error {
|
|
833
|
+
diagnostic;
|
|
834
|
+
constructor(diagnostic) {
|
|
835
|
+
super(diagnostic.message, { cause: diagnostic.cause });
|
|
836
|
+
this.name = "DiagnosticError";
|
|
837
|
+
this.diagnostic = diagnostic;
|
|
838
|
+
}
|
|
839
|
+
};
|
|
840
|
+
/**
|
|
841
|
+
* Structural check for a {@link DiagnosticError}, including one thrown from a duplicated
|
|
842
|
+
* `@kubb/core` copy where `instanceof` fails. Matches on the `name` and a `diagnostic`
|
|
843
|
+
* that carries a `code`.
|
|
844
|
+
*/
|
|
845
|
+
function isDiagnosticError(error) {
|
|
846
|
+
if (error instanceof DiagnosticError) return true;
|
|
847
|
+
return error instanceof Error && error.name === "DiagnosticError" && "diagnostic" in error && typeof error.diagnostic === "object" && error.diagnostic !== null && typeof error.diagnostic?.code === "string";
|
|
848
|
+
}
|
|
849
|
+
/**
|
|
850
|
+
* Explanation for every {@link diagnosticCode}. Use {@link Diagnostics.explain} to look one up
|
|
851
|
+
* and `Diagnostics.docsUrl` for the matching kubb.dev page.
|
|
852
|
+
*/
|
|
853
|
+
const diagnosticCatalog = {
|
|
854
|
+
[diagnosticCode.unknown]: {
|
|
855
|
+
title: "Unknown error",
|
|
856
|
+
cause: "An error was thrown without a stable Kubb code, so it is reported as-is.",
|
|
857
|
+
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."
|
|
858
|
+
},
|
|
859
|
+
[diagnosticCode.inputNotFound]: {
|
|
860
|
+
title: "Input not found",
|
|
861
|
+
cause: "The file or URL set in `input.path` (or passed as `kubb generate PATH`) could not be read.",
|
|
862
|
+
fix: "Check that the path or URL exists and is readable, then set it in `input.path` or pass it on the CLI."
|
|
863
|
+
},
|
|
864
|
+
[diagnosticCode.inputRequired]: {
|
|
865
|
+
title: "Input required",
|
|
866
|
+
cause: "An adapter is configured but no `input` was provided.",
|
|
867
|
+
fix: "Set `input.path` (a file or URL) or `input.data` (an inline spec) in your Kubb config."
|
|
868
|
+
},
|
|
869
|
+
[diagnosticCode.refNotFound]: {
|
|
870
|
+
title: "Reference not found",
|
|
871
|
+
cause: "A `$ref` could not be resolved in the source document.",
|
|
872
|
+
fix: "Add the missing definition (for example under `components.schemas`) or fix the `$ref`. Run `kubb validate` to check the spec."
|
|
873
|
+
},
|
|
874
|
+
[diagnosticCode.invalidServerVariable]: {
|
|
875
|
+
title: "Invalid server variable",
|
|
876
|
+
cause: "A server variable value is not allowed by its `enum`.",
|
|
877
|
+
fix: "Use one of the values listed in the server variable `enum`, or update the spec."
|
|
878
|
+
},
|
|
879
|
+
[diagnosticCode.pluginNotFound]: {
|
|
880
|
+
title: "Plugin not found",
|
|
881
|
+
cause: "A plugin that another plugin depends on is missing from the config.",
|
|
882
|
+
fix: "Add the required plugin to the `plugins` array in kubb.config.ts, or remove the dependency on it."
|
|
883
|
+
},
|
|
884
|
+
[diagnosticCode.pluginFailed]: {
|
|
885
|
+
title: "Plugin failed",
|
|
886
|
+
cause: "A plugin threw while generating, or reported an error through `ctx.error`.",
|
|
887
|
+
fix: "Read the underlying error and check the plugin options and the schema or operation it failed on."
|
|
888
|
+
},
|
|
889
|
+
[diagnosticCode.pluginWarning]: {
|
|
890
|
+
title: "Plugin warning",
|
|
891
|
+
cause: "A plugin reported a non-fatal warning through `ctx.warn`.",
|
|
892
|
+
fix: "Review the message. It does not fail the build; adjust the plugin options or input if the warning is unwanted."
|
|
893
|
+
},
|
|
894
|
+
[diagnosticCode.pluginInfo]: {
|
|
895
|
+
title: "Plugin info",
|
|
896
|
+
cause: "A plugin reported an informational message through `ctx.info`.",
|
|
897
|
+
fix: "Informational only. No action is required."
|
|
898
|
+
},
|
|
899
|
+
[diagnosticCode.unsupportedFormat]: {
|
|
900
|
+
title: "Unsupported format",
|
|
901
|
+
cause: "A schema uses a `format` Kubb does not map to a specific type, so it falls back to the base type.",
|
|
902
|
+
fix: "Use a format Kubb supports, or handle the custom format with a parser or plugin."
|
|
903
|
+
},
|
|
904
|
+
[diagnosticCode.deprecated]: {
|
|
905
|
+
title: "Deprecated",
|
|
906
|
+
cause: "A referenced schema or operation is marked `deprecated`.",
|
|
907
|
+
fix: "Migrate off the deprecated definition if the warning is unwanted."
|
|
908
|
+
},
|
|
909
|
+
[diagnosticCode.adapterRequired]: {
|
|
910
|
+
title: "Adapter required",
|
|
911
|
+
cause: "An action needs an adapter (for example opening Kubb Studio) but none is configured.",
|
|
912
|
+
fix: "Set `adapter` in kubb.config.ts, for example `adapterOas()`."
|
|
913
|
+
},
|
|
914
|
+
[diagnosticCode.devtoolsInvalid]: {
|
|
915
|
+
title: "Invalid devtools config",
|
|
916
|
+
cause: "The `devtools` config is set to something other than an object.",
|
|
917
|
+
fix: "Set `devtools` to an options object, or remove it to disable Kubb Studio."
|
|
918
|
+
},
|
|
919
|
+
[diagnosticCode.pathTraversal]: {
|
|
920
|
+
title: "Path traversal",
|
|
921
|
+
cause: "A resolved output path escaped the output directory, which can stem from a path traversal in the spec or a misconfigured `group.name`.",
|
|
922
|
+
fix: "Keep generated paths within the output directory. Review the `group.name` function and the names coming from the spec."
|
|
923
|
+
},
|
|
924
|
+
[diagnosticCode.hookFailed]: {
|
|
925
|
+
title: "Hook failed",
|
|
926
|
+
cause: "A post-generate shell hook (`hooks.done`) exited with a non-zero status.",
|
|
927
|
+
fix: "Check the command is installed and correct, and run it manually to see the error."
|
|
928
|
+
},
|
|
929
|
+
[diagnosticCode.formatFailed]: {
|
|
930
|
+
title: "Format failed",
|
|
931
|
+
cause: "The formatter pass over the generated files failed.",
|
|
932
|
+
fix: "Check the formatter (oxfmt, biome, or prettier) is installed and its config is valid, then run it manually on the output."
|
|
933
|
+
},
|
|
934
|
+
[diagnosticCode.lintFailed]: {
|
|
935
|
+
title: "Lint failed",
|
|
936
|
+
cause: "The linter pass over the generated files failed.",
|
|
937
|
+
fix: "Check the linter (oxlint, biome, or eslint) is installed and its config is valid, then run it manually on the output."
|
|
938
|
+
},
|
|
939
|
+
[diagnosticCode.performance]: {
|
|
940
|
+
title: "Performance",
|
|
941
|
+
cause: "Not a failure. Records a plugin’s elapsed time, summed into the run total.",
|
|
942
|
+
fix: "No action. This is an informational metric."
|
|
943
|
+
},
|
|
944
|
+
[diagnosticCode.updateAvailable]: {
|
|
945
|
+
title: "Update available",
|
|
946
|
+
cause: "A newer Kubb version is published on npm than the one running.",
|
|
947
|
+
fix: "Update the `@kubb/*` packages, for example `npm install -g @kubb/cli`, to get the latest fixes."
|
|
948
|
+
}
|
|
949
|
+
};
|
|
950
|
+
/**
|
|
951
|
+
* Static helpers for working with {@link Diagnostic}s, plus the run-scoped sink
|
|
952
|
+
* that lets deep code report a diagnostic without threading a callback.
|
|
953
|
+
*
|
|
954
|
+
* The sink lives in a single `AsyncLocalStorage` in the `@kubb/core` bundle.
|
|
955
|
+
* `Diagnostics.scope` activates it for a run, so anything inside that run (the
|
|
956
|
+
* adapter parse, a lazily consumed stream, a generator) reports through
|
|
957
|
+
* `Diagnostics.report` and lands in the same run.
|
|
958
|
+
*/
|
|
959
|
+
var Diagnostics = class Diagnostics {
|
|
960
|
+
static #reporterStorage = new node_async_hooks.AsyncLocalStorage();
|
|
961
|
+
/**
|
|
962
|
+
* Runs `fn` with `sink` as the active diagnostic sink for the whole async
|
|
963
|
+
* subtree, so {@link Diagnostics.report} reaches it from anywhere inside.
|
|
964
|
+
*/
|
|
965
|
+
static scope(sink, fn) {
|
|
966
|
+
return Diagnostics.#reporterStorage.run(sink, fn);
|
|
967
|
+
}
|
|
968
|
+
/**
|
|
969
|
+
* Collects a diagnostic into the active build via the run-scoped sink, without throwing.
|
|
970
|
+
* Returns `true` when a run consumed it, `false` when called outside a {@link Diagnostics.scope}
|
|
971
|
+
* (so callers can fall back to throwing). Use a `warning`/`info` severity for non-fatal issues.
|
|
972
|
+
* For rendering a diagnostic live on the hook bus, use {@link Diagnostics.emit} instead.
|
|
973
|
+
*/
|
|
974
|
+
static report(diagnostic) {
|
|
975
|
+
const sink = Diagnostics.#reporterStorage.getStore();
|
|
976
|
+
if (!sink) return false;
|
|
977
|
+
sink(diagnostic);
|
|
978
|
+
return true;
|
|
979
|
+
}
|
|
980
|
+
/**
|
|
981
|
+
* Emits a diagnostic on the run's `kubb:diagnostic` event so the loggers render it live.
|
|
982
|
+
* Use it instead of calling `hooks.emit('kubb:diagnostic', ...)` directly. To collect a
|
|
983
|
+
* diagnostic into the build result from deep in a run, use {@link Diagnostics.report} instead.
|
|
984
|
+
*/
|
|
985
|
+
static async emit(hooks, diagnostic) {
|
|
986
|
+
await hooks.emit("kubb:diagnostic", { diagnostic });
|
|
987
|
+
}
|
|
988
|
+
/**
|
|
989
|
+
* Coerces any thrown value into a {@link ProblemDiagnostic}. A {@link DiagnosticError}
|
|
990
|
+
* keeps its structured data, and anything else becomes a `KUBB_UNKNOWN` error.
|
|
991
|
+
*/
|
|
992
|
+
static from(error) {
|
|
993
|
+
const seen = /* @__PURE__ */ new Set();
|
|
994
|
+
let current = error;
|
|
995
|
+
let root;
|
|
996
|
+
while (current instanceof Error && !seen.has(current)) {
|
|
997
|
+
if (isDiagnosticError(current)) return current.diagnostic;
|
|
998
|
+
seen.add(current);
|
|
999
|
+
root = current;
|
|
1000
|
+
current = current.cause;
|
|
1001
|
+
}
|
|
1002
|
+
return {
|
|
1003
|
+
code: diagnosticCode.unknown,
|
|
1004
|
+
severity: "error",
|
|
1005
|
+
message: root ? root.message : getErrorMessage(error),
|
|
1006
|
+
cause: root
|
|
1007
|
+
};
|
|
1008
|
+
}
|
|
1009
|
+
/**
|
|
1010
|
+
* Builds a per-plugin performance record. Reporters sum these into the run total.
|
|
1011
|
+
*/
|
|
1012
|
+
static performance({ plugin, duration }) {
|
|
1013
|
+
return {
|
|
1014
|
+
kind: "performance",
|
|
1015
|
+
code: diagnosticCode.performance,
|
|
1016
|
+
severity: "info",
|
|
1017
|
+
message: `${plugin} generated in ${Math.round(duration)}ms`,
|
|
1018
|
+
plugin,
|
|
1019
|
+
duration
|
|
1020
|
+
};
|
|
1021
|
+
}
|
|
1022
|
+
/**
|
|
1023
|
+
* Builds the version-update notice shown when a newer Kubb is published on npm.
|
|
1024
|
+
*/
|
|
1025
|
+
static update({ currentVersion, latestVersion }) {
|
|
1026
|
+
return {
|
|
1027
|
+
kind: "update",
|
|
1028
|
+
code: diagnosticCode.updateAvailable,
|
|
1029
|
+
severity: "info",
|
|
1030
|
+
message: `Update available: v${currentVersion} → v${latestVersion}. Run \`npm install -g @kubb/cli\` to update.`,
|
|
1031
|
+
currentVersion,
|
|
1032
|
+
latestVersion
|
|
1033
|
+
};
|
|
1034
|
+
}
|
|
1035
|
+
/**
|
|
1036
|
+
* True when any diagnostic is an error, the severity that fails a build. Non-error
|
|
1037
|
+
* diagnostics are ignored.
|
|
1038
|
+
*/
|
|
1039
|
+
static hasError(diagnostics) {
|
|
1040
|
+
return diagnostics.some((diagnostic) => diagnostic.severity === "error");
|
|
1041
|
+
}
|
|
1042
|
+
/**
|
|
1043
|
+
* Names of the plugins that failed, deduped, derived from the error diagnostics
|
|
1044
|
+
* that carry a `plugin`.
|
|
1045
|
+
*/
|
|
1046
|
+
static failedPlugins(diagnostics) {
|
|
1047
|
+
const names = /* @__PURE__ */ new Set();
|
|
1048
|
+
for (const diagnostic of diagnostics) if (diagnostic.severity === "error" && diagnostic.plugin) names.add(diagnostic.plugin);
|
|
1049
|
+
return [...names];
|
|
1050
|
+
}
|
|
1051
|
+
/**
|
|
1052
|
+
* Counts `problem` diagnostics by severity for the run summary. `timing`
|
|
1053
|
+
* diagnostics are ignored.
|
|
1054
|
+
*/
|
|
1055
|
+
static count(diagnostics) {
|
|
1056
|
+
let errors = 0;
|
|
1057
|
+
let warnings = 0;
|
|
1058
|
+
let infos = 0;
|
|
1059
|
+
for (const diagnostic of diagnostics) {
|
|
1060
|
+
if (!isProblemDiagnostic(diagnostic)) continue;
|
|
1061
|
+
if (diagnostic.severity === "error") errors += 1;
|
|
1062
|
+
else if (diagnostic.severity === "warning") warnings += 1;
|
|
1063
|
+
else infos += 1;
|
|
1064
|
+
}
|
|
1065
|
+
return {
|
|
1066
|
+
errors,
|
|
1067
|
+
warnings,
|
|
1068
|
+
infos
|
|
1069
|
+
};
|
|
1070
|
+
}
|
|
1071
|
+
/**
|
|
1072
|
+
* Drops duplicate `problem` diagnostics that share a code, location pointer, and
|
|
1073
|
+
* plugin, so the same issue reported across several passes is shown once. Non-problem
|
|
1074
|
+
* diagnostics are always kept.
|
|
1075
|
+
*/
|
|
1076
|
+
static dedupe(diagnostics) {
|
|
1077
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1078
|
+
const result = [];
|
|
1079
|
+
for (const diagnostic of diagnostics) {
|
|
1080
|
+
if (!isProblemDiagnostic(diagnostic)) {
|
|
1081
|
+
result.push(diagnostic);
|
|
1082
|
+
continue;
|
|
1083
|
+
}
|
|
1084
|
+
const pointer = diagnostic.location && "pointer" in diagnostic.location ? diagnostic.location.pointer : "";
|
|
1085
|
+
const key = `${diagnostic.code} ${pointer} ${diagnostic.plugin ?? ""}`;
|
|
1086
|
+
if (seen.has(key)) continue;
|
|
1087
|
+
seen.add(key);
|
|
1088
|
+
result.push(diagnostic);
|
|
1089
|
+
}
|
|
1090
|
+
return result;
|
|
1091
|
+
}
|
|
1092
|
+
/**
|
|
1093
|
+
* Builds the kubb.dev docs URL for a diagnostic code, e.g.
|
|
1094
|
+
* `KUBB_REF_NOT_FOUND` → `https://kubb.dev/docs/5.x/reference/diagnostics/kubb-ref-not-found`.
|
|
1095
|
+
*/
|
|
1096
|
+
static docsUrl(code) {
|
|
1097
|
+
return `https://kubb.dev/docs/${docsMajor}.x/reference/diagnostics/${code.toLowerCase().replaceAll("_", "-")}`;
|
|
1098
|
+
}
|
|
1099
|
+
/**
|
|
1100
|
+
* The catalog entry for a code: its title, cause, and fix. Mirrors the kubb.dev
|
|
1101
|
+
* `/diagnostics/<slug>` page.
|
|
1102
|
+
*/
|
|
1103
|
+
static explain(code) {
|
|
1104
|
+
return diagnosticCatalog[code];
|
|
1105
|
+
}
|
|
1106
|
+
/**
|
|
1107
|
+
* Reduces a diagnostic to its JSON-safe fields plus a `docsUrl`, for machine-readable
|
|
1108
|
+
* consumers. The `cause`, `kind`, and `duration` are dropped, and absent optional
|
|
1109
|
+
* fields are omitted rather than set to `undefined`.
|
|
1110
|
+
*/
|
|
1111
|
+
static serialize(diagnostic) {
|
|
1112
|
+
const problem = isProblemDiagnostic(diagnostic) ? diagnostic : void 0;
|
|
1113
|
+
return {
|
|
1114
|
+
code: diagnostic.code,
|
|
1115
|
+
severity: diagnostic.severity,
|
|
1116
|
+
message: diagnostic.message,
|
|
1117
|
+
...problem?.location ? { location: problem.location } : {},
|
|
1118
|
+
...problem?.help ? { help: problem.help } : {},
|
|
1119
|
+
...problem?.plugin ? { plugin: problem.plugin } : {},
|
|
1120
|
+
...diagnostic.code === diagnosticCode.unknown ? {} : { docsUrl: Diagnostics.docsUrl(diagnostic.code) }
|
|
1121
|
+
};
|
|
1122
|
+
}
|
|
658
1123
|
};
|
|
659
1124
|
//#endregion
|
|
660
1125
|
//#region src/definePlugin.ts
|
|
@@ -764,7 +1229,7 @@ function defaultResolver(name, type) {
|
|
|
764
1229
|
return camelCase(name);
|
|
765
1230
|
}
|
|
766
1231
|
/**
|
|
767
|
-
* Default option resolver
|
|
1232
|
+
* Default option resolver. Applies include/exclude filters and merges matching override options.
|
|
768
1233
|
*
|
|
769
1234
|
* Returns `null` when the node is filtered out by an `exclude` rule or not matched by any `include` rule.
|
|
770
1235
|
*
|
|
@@ -800,7 +1265,7 @@ function computeOptions(node, options, exclude, include, override) {
|
|
|
800
1265
|
if ((0, _kubb_ast.isSchemaNode)(node)) {
|
|
801
1266
|
if (exclude.some(({ type, pattern }) => matchesSchemaPattern(node, type, pattern) === true)) return null;
|
|
802
1267
|
if (include) {
|
|
803
|
-
const applicable = include.map(({ type, pattern }) => matchesSchemaPattern(node, type, pattern)).filter((
|
|
1268
|
+
const applicable = include.map(({ type, pattern }) => matchesSchemaPattern(node, type, pattern)).filter((result) => result !== null);
|
|
804
1269
|
if (applicable.length > 0 && !applicable.includes(true)) return null;
|
|
805
1270
|
}
|
|
806
1271
|
const overrideOptions = override.find(({ type, pattern }) => matchesSchemaPattern(node, type, pattern) === true)?.options;
|
|
@@ -873,8 +1338,8 @@ function defaultResolvePath({ baseName, pathMode, tag, path: groupPath }, { root
|
|
|
873
1338
|
const result = (() => {
|
|
874
1339
|
if (group && (groupPath || tag)) {
|
|
875
1340
|
const groupValue = group.type === "path" ? groupPath : tag;
|
|
876
|
-
const defaultName = group.type === "tag" ? ({ group:
|
|
877
|
-
const segment =
|
|
1341
|
+
const defaultName = group.type === "tag" ? ({ group: groupName }) => `${camelCase(groupName)}Controller` : ({ group: groupName }) => {
|
|
1342
|
+
const segment = groupName.split("/").filter((part) => part !== "" && part !== "." && part !== "..")[0];
|
|
878
1343
|
return segment ? camelCase(segment) : "";
|
|
879
1344
|
};
|
|
880
1345
|
const resolveName = group.name ?? defaultName;
|
|
@@ -884,7 +1349,13 @@ function defaultResolvePath({ baseName, pathMode, tag, path: groupPath }, { root
|
|
|
884
1349
|
})();
|
|
885
1350
|
const outputDir = node_path.default.resolve(root, output.path);
|
|
886
1351
|
const outputDirWithSep = outputDir.endsWith(node_path.default.sep) ? outputDir : `${outputDir}${node_path.default.sep}`;
|
|
887
|
-
if (result !== outputDir && !result.startsWith(outputDirWithSep)) throw new
|
|
1352
|
+
if (result !== outputDir && !result.startsWith(outputDirWithSep)) throw new DiagnosticError({
|
|
1353
|
+
code: diagnosticCode.pathTraversal,
|
|
1354
|
+
severity: "error",
|
|
1355
|
+
message: `Resolved path "${result}" is outside the output directory "${outputDir}".`,
|
|
1356
|
+
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.",
|
|
1357
|
+
location: { kind: "config" }
|
|
1358
|
+
});
|
|
888
1359
|
return result;
|
|
889
1360
|
}
|
|
890
1361
|
/**
|
|
@@ -892,7 +1363,7 @@ function defaultResolvePath({ baseName, pathMode, tag, path: groupPath }, { root
|
|
|
892
1363
|
*
|
|
893
1364
|
* Resolves a `FileNode` by combining name resolution (`resolver.default`) with
|
|
894
1365
|
* path resolution (`resolver.resolvePath`). The resolved file always has empty
|
|
895
|
-
* `sources`, `imports`, and `exports` arrays
|
|
1366
|
+
* `sources`, `imports`, and `exports` arrays, which consumers populate separately.
|
|
896
1367
|
*
|
|
897
1368
|
* In `single` mode the name is omitted and the file sits directly in the output directory.
|
|
898
1369
|
*
|
|
@@ -968,7 +1439,7 @@ function buildDefaultBanner({ title, description, version, config }) {
|
|
|
968
1439
|
}
|
|
969
1440
|
}
|
|
970
1441
|
/**
|
|
971
|
-
* Default banner resolver
|
|
1442
|
+
* Default banner resolver. Returns the banner string for a generated file.
|
|
972
1443
|
*
|
|
973
1444
|
* A user-supplied `output.banner` overrides the default Kubb "Generated by Kubb" notice.
|
|
974
1445
|
* When no `output.banner` is set, the Kubb notice is used (including `title` and `version`
|
|
@@ -997,7 +1468,7 @@ function buildDefaultBanner({ title, description, version, config }) {
|
|
|
997
1468
|
* // → ''
|
|
998
1469
|
* ```
|
|
999
1470
|
*
|
|
1000
|
-
* @example No user banner
|
|
1471
|
+
* @example No user banner, Kubb notice with OAS metadata
|
|
1001
1472
|
* ```ts
|
|
1002
1473
|
* defaultResolveBanner(meta, { config })
|
|
1003
1474
|
* // → '/** Generated by Kubb ... Title: Pet Store ... *\/'
|
|
@@ -1023,7 +1494,7 @@ function defaultResolveBanner(meta, { output, config, file }) {
|
|
|
1023
1494
|
});
|
|
1024
1495
|
}
|
|
1025
1496
|
/**
|
|
1026
|
-
* Default footer resolver
|
|
1497
|
+
* Default footer resolver. Returns the footer string for a generated file.
|
|
1027
1498
|
*
|
|
1028
1499
|
* - When `output.footer` is a function, calls it with the file's `BannerMeta` and returns the result.
|
|
1029
1500
|
* - When `output.footer` is a string, returns it directly.
|
|
@@ -1055,11 +1526,11 @@ function defaultResolveFooter(meta, { output, file }) {
|
|
|
1055
1526
|
* name casing, include/exclude/override filtering, output path computation,
|
|
1056
1527
|
* and file construction. Supply your own to override any of them:
|
|
1057
1528
|
*
|
|
1058
|
-
* - `default`
|
|
1059
|
-
* - `resolveOptions`
|
|
1060
|
-
* - `resolvePath`
|
|
1061
|
-
* - `resolveFile`
|
|
1062
|
-
* - `resolveBanner`
|
|
1529
|
+
* - `default` sets the name casing strategy (camelCase or PascalCase).
|
|
1530
|
+
* - `resolveOptions` does include/exclude/override filtering.
|
|
1531
|
+
* - `resolvePath` computes the output path.
|
|
1532
|
+
* - `resolveFile` builds the full `FileNode`.
|
|
1533
|
+
* - `resolveBanner` and `resolveFooter` produce the top and bottom of file text.
|
|
1063
1534
|
*
|
|
1064
1535
|
* Methods in the returned object can call sibling resolver methods via `this`,
|
|
1065
1536
|
* which keeps custom rules small (`this.default(name, 'type')` to delegate).
|
|
@@ -1108,7 +1579,7 @@ function defineResolver(build) {
|
|
|
1108
1579
|
* Encodes an `InputNode` as a compressed, URL-safe string.
|
|
1109
1580
|
*
|
|
1110
1581
|
* The JSON representation is deflate-compressed with {@link deflateSync} before
|
|
1111
|
-
* base64url encoding, which typically reduces payload size by 70
|
|
1582
|
+
* base64url encoding, which typically reduces payload size by 70, 80 % and
|
|
1112
1583
|
* keeps URLs well within browser and server path-length limits.
|
|
1113
1584
|
*/
|
|
1114
1585
|
function encodeAst(input) {
|
|
@@ -1125,8 +1596,7 @@ function getStudioUrl(input, studioUrl, options = {}) {
|
|
|
1125
1596
|
return `${studioUrl.replace(/\/$/, "")}${options.ast ? "/ast" : ""}?root=${encodeAst(input)}`;
|
|
1126
1597
|
}
|
|
1127
1598
|
/**
|
|
1128
|
-
* Opens the Kubb Studio URL for the given `InputNode` in the default browser
|
|
1129
|
-
*
|
|
1599
|
+
* Opens the Kubb Studio URL for the given `InputNode` in the default browser, *
|
|
1130
1600
|
* Falls back to printing the URL if the browser cannot be launched.
|
|
1131
1601
|
*/
|
|
1132
1602
|
async function openInStudio(input, studioUrl, options = {}) {
|
|
@@ -1181,18 +1651,13 @@ function compareFiles(a, b) {
|
|
|
1181
1651
|
* ```
|
|
1182
1652
|
*/
|
|
1183
1653
|
var FileManager = class {
|
|
1184
|
-
#cache = /* @__PURE__ */ new Map();
|
|
1185
|
-
#sorted = null;
|
|
1186
|
-
#onUpsert = null;
|
|
1187
1654
|
/**
|
|
1188
|
-
*
|
|
1189
|
-
* `add`
|
|
1190
|
-
* without keeping its own scan-based diff. Single subscriber by design —
|
|
1191
|
-
* setting again replaces the previous callback. Pass `null` to detach.
|
|
1655
|
+
* Subscribe to file-store changes. Listeners on `upsert` see each resolved file as it lands
|
|
1656
|
+
* through `add` or `upsert`.
|
|
1192
1657
|
*/
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1658
|
+
hooks = new AsyncEventEmitter();
|
|
1659
|
+
#cache = /* @__PURE__ */ new Map();
|
|
1660
|
+
#sorted = null;
|
|
1196
1661
|
add(...files) {
|
|
1197
1662
|
return this.#store(files, false);
|
|
1198
1663
|
}
|
|
@@ -1207,7 +1672,7 @@ var FileManager = class {
|
|
|
1207
1672
|
const merged = existing && mergeExisting ? (0, _kubb_ast.createFile)(mergeFile(existing, file)) : (0, _kubb_ast.createFile)(file);
|
|
1208
1673
|
this.#cache.set(merged.path, merged);
|
|
1209
1674
|
resolved.push(merged);
|
|
1210
|
-
this
|
|
1675
|
+
this.hooks.emit("upsert", merged);
|
|
1211
1676
|
}
|
|
1212
1677
|
if (resolved.length > 0) this.#sorted = null;
|
|
1213
1678
|
return resolved;
|
|
@@ -1232,18 +1697,19 @@ var FileManager = class {
|
|
|
1232
1697
|
this.#sorted = null;
|
|
1233
1698
|
}
|
|
1234
1699
|
/**
|
|
1235
|
-
* Releases all stored files. Called by the core after
|
|
1700
|
+
* Releases all stored files and clears every `hooks` listener. Called by the core after
|
|
1701
|
+
* `kubb:build:end`.
|
|
1236
1702
|
*/
|
|
1237
1703
|
dispose() {
|
|
1238
1704
|
this.clear();
|
|
1239
|
-
this
|
|
1705
|
+
this.hooks.removeAll();
|
|
1240
1706
|
}
|
|
1241
1707
|
[Symbol.dispose]() {
|
|
1242
1708
|
this.dispose();
|
|
1243
1709
|
}
|
|
1244
1710
|
/**
|
|
1245
1711
|
* All stored files in stable sort order (shortest path first, barrel files
|
|
1246
|
-
* last within a length bucket). Returns a cached view
|
|
1712
|
+
* last within a length bucket). Returns a cached view, do not mutate.
|
|
1247
1713
|
*/
|
|
1248
1714
|
get files() {
|
|
1249
1715
|
return this.#sorted ??= [...this.#cache.values()].sort(compareFiles);
|
|
@@ -1256,32 +1722,57 @@ function joinSources(file) {
|
|
|
1256
1722
|
if (sources.length === 0) return "";
|
|
1257
1723
|
const parts = [];
|
|
1258
1724
|
for (const source of sources) {
|
|
1259
|
-
const
|
|
1260
|
-
if (
|
|
1725
|
+
const text = (0, _kubb_ast.extractStringsFromNodes)(source.nodes);
|
|
1726
|
+
if (text) parts.push(text);
|
|
1261
1727
|
}
|
|
1262
1728
|
return parts.join("\n\n");
|
|
1263
1729
|
}
|
|
1264
1730
|
/**
|
|
1265
|
-
*
|
|
1266
|
-
*
|
|
1731
|
+
* Turns `FileNode`s into source strings and writes them to storage.
|
|
1732
|
+
*
|
|
1733
|
+
* Two modes share the same instance. Stateless mode (`parse`, `stream`, `run`) just runs the
|
|
1734
|
+
* conversion. Queue mode (`enqueue`, `flush`, `drain`) buffers files deduped by path and
|
|
1735
|
+
* writes each batch through storage with up to `STREAM_FLUSH_EVERY` requests in flight.
|
|
1736
|
+
*
|
|
1737
|
+
* `flush` does not wait for its batch to finish, so dispatch can overlap with IO. The next
|
|
1738
|
+
* `flush` or `drain` picks the in-flight batch up. `drain` blocks until everything has been
|
|
1739
|
+
* written and is meant for the end of a build.
|
|
1267
1740
|
*
|
|
1268
|
-
*
|
|
1741
|
+
* To surface build-level hook signals (`kubb:files:processing:*` and friends) subscribe to
|
|
1742
|
+
* `hooks` and re-emit on the kubb bus.
|
|
1269
1743
|
*/
|
|
1270
1744
|
var FileProcessor = class {
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1745
|
+
hooks = new AsyncEventEmitter();
|
|
1746
|
+
#parsers;
|
|
1747
|
+
#storage;
|
|
1748
|
+
#extension;
|
|
1749
|
+
#pending = /* @__PURE__ */ new Map();
|
|
1750
|
+
#runningFlush = null;
|
|
1751
|
+
constructor(options) {
|
|
1752
|
+
this.#parsers = options.parsers ?? null;
|
|
1753
|
+
this.#storage = options.storage;
|
|
1754
|
+
this.#extension = options.extension ?? null;
|
|
1755
|
+
}
|
|
1756
|
+
/**
|
|
1757
|
+
* Files waiting in the queue.
|
|
1758
|
+
*/
|
|
1759
|
+
get size() {
|
|
1760
|
+
return this.#pending.size;
|
|
1761
|
+
}
|
|
1762
|
+
parse(file) {
|
|
1763
|
+
const parsers = this.#parsers;
|
|
1764
|
+
const parseExtName = this.#extension?.[file.extname] || void 0;
|
|
1274
1765
|
if (!parsers || !file.extname) return joinSources(file);
|
|
1275
1766
|
const parser = parsers.get(file.extname);
|
|
1276
1767
|
if (!parser) return joinSources(file);
|
|
1277
1768
|
return parser.parse(file, { extname: parseExtName });
|
|
1278
1769
|
}
|
|
1279
|
-
*stream(files
|
|
1770
|
+
*stream(files) {
|
|
1280
1771
|
const total = files.length;
|
|
1281
1772
|
if (total === 0) return;
|
|
1282
1773
|
let processed = 0;
|
|
1283
1774
|
for (const file of files) {
|
|
1284
|
-
const source = this.parse(file
|
|
1775
|
+
const source = this.parse(file);
|
|
1285
1776
|
processed++;
|
|
1286
1777
|
yield {
|
|
1287
1778
|
file,
|
|
@@ -1292,29 +1783,157 @@ var FileProcessor = class {
|
|
|
1292
1783
|
};
|
|
1293
1784
|
}
|
|
1294
1785
|
}
|
|
1295
|
-
async run(files
|
|
1296
|
-
await this.
|
|
1297
|
-
for (const { file, source, processed, total, percentage } of this.stream(files
|
|
1786
|
+
async run(files) {
|
|
1787
|
+
await this.hooks.emit("start", files);
|
|
1788
|
+
for (const { file, source, processed, total, percentage } of this.stream(files)) await this.hooks.emit("update", {
|
|
1298
1789
|
file,
|
|
1299
1790
|
source,
|
|
1300
1791
|
processed,
|
|
1301
1792
|
percentage,
|
|
1302
1793
|
total
|
|
1303
1794
|
});
|
|
1304
|
-
await this.
|
|
1795
|
+
await this.hooks.emit("end", files);
|
|
1305
1796
|
return files;
|
|
1306
1797
|
}
|
|
1307
1798
|
/**
|
|
1308
|
-
*
|
|
1799
|
+
* Adds a file to the next flush. A later `enqueue` for the same path replaces the previous
|
|
1800
|
+
* entry, matching `FileManager.upsert`. Fires the `enqueue` event.
|
|
1801
|
+
*/
|
|
1802
|
+
enqueue(file) {
|
|
1803
|
+
this.#pending.set(file.path, file);
|
|
1804
|
+
this.hooks.emit("enqueue", file);
|
|
1805
|
+
}
|
|
1806
|
+
/**
|
|
1807
|
+
* Starts processing the queued files. Waits for any previous flush to finish (so two
|
|
1808
|
+
* batches never run together) and then returns without waiting for the new one. The next
|
|
1809
|
+
* `flush` or `drain` picks up the in-flight task.
|
|
1810
|
+
*/
|
|
1811
|
+
async flush() {
|
|
1812
|
+
if (this.#runningFlush) await this.#runningFlush;
|
|
1813
|
+
if (this.#pending.size === 0) return;
|
|
1814
|
+
const batch = [...this.#pending.values()];
|
|
1815
|
+
this.#pending.clear();
|
|
1816
|
+
this.#runningFlush = this.#processAndWrite(batch).finally(() => {
|
|
1817
|
+
this.#runningFlush = null;
|
|
1818
|
+
});
|
|
1819
|
+
}
|
|
1820
|
+
/**
|
|
1821
|
+
* Waits for the in-flight flush and writes any files still queued. Fires the `drain` event
|
|
1822
|
+
* when both are done.
|
|
1823
|
+
*/
|
|
1824
|
+
async drain() {
|
|
1825
|
+
if (this.#runningFlush) await this.#runningFlush;
|
|
1826
|
+
if (this.#pending.size > 0) {
|
|
1827
|
+
const batch = [...this.#pending.values()];
|
|
1828
|
+
this.#pending.clear();
|
|
1829
|
+
await this.#processAndWrite(batch);
|
|
1830
|
+
}
|
|
1831
|
+
await this.hooks.emit("drain");
|
|
1832
|
+
}
|
|
1833
|
+
async #processAndWrite(files) {
|
|
1834
|
+
const storage = this.#storage;
|
|
1835
|
+
await this.hooks.emit("start", files);
|
|
1836
|
+
const items = [...this.stream(files)];
|
|
1837
|
+
for (const item of items) await this.hooks.emit("update", item);
|
|
1838
|
+
const queue = [];
|
|
1839
|
+
for (const { file, source } of items) if (source) {
|
|
1840
|
+
queue.push(storage.setItem(file.path, source));
|
|
1841
|
+
if (queue.length >= 50) await Promise.all(queue.splice(0));
|
|
1842
|
+
}
|
|
1843
|
+
await Promise.all(queue);
|
|
1844
|
+
await this.hooks.emit("end", files);
|
|
1845
|
+
}
|
|
1846
|
+
/**
|
|
1847
|
+
* Clears every listener and the pending queue.
|
|
1309
1848
|
*/
|
|
1310
1849
|
dispose() {
|
|
1311
|
-
this.
|
|
1850
|
+
this.hooks.removeAll();
|
|
1851
|
+
this.#pending.clear();
|
|
1312
1852
|
}
|
|
1313
1853
|
[Symbol.dispose]() {
|
|
1314
1854
|
this.dispose();
|
|
1315
1855
|
}
|
|
1316
1856
|
};
|
|
1317
1857
|
//#endregion
|
|
1858
|
+
//#region src/HookRegistry.ts
|
|
1859
|
+
/**
|
|
1860
|
+
* Listener bookkeeping around an `AsyncEventEmitter`. Listeners attached through `register`
|
|
1861
|
+
* stay on the emitter but are tracked so `dispose()` removes only them, listeners attached
|
|
1862
|
+
* directly via `emitter.on(...)` survive.
|
|
1863
|
+
*/
|
|
1864
|
+
var HookRegistry = class {
|
|
1865
|
+
#emitter;
|
|
1866
|
+
#entries = /* @__PURE__ */ new Set();
|
|
1867
|
+
constructor(options) {
|
|
1868
|
+
this.#emitter = options.emitter;
|
|
1869
|
+
}
|
|
1870
|
+
get emitter() {
|
|
1871
|
+
return this.#emitter;
|
|
1872
|
+
}
|
|
1873
|
+
get size() {
|
|
1874
|
+
return this.#entries.size;
|
|
1875
|
+
}
|
|
1876
|
+
register(options) {
|
|
1877
|
+
this.#emitter.on(options.event, options.handler);
|
|
1878
|
+
this.#entries.add(options);
|
|
1879
|
+
}
|
|
1880
|
+
dispose() {
|
|
1881
|
+
for (const entry of this.#entries) this.#emitter.off(entry.event, entry.handler);
|
|
1882
|
+
this.#entries.clear();
|
|
1883
|
+
}
|
|
1884
|
+
};
|
|
1885
|
+
//#endregion
|
|
1886
|
+
//#region src/Transform.ts
|
|
1887
|
+
/**
|
|
1888
|
+
* Holds one `Visitor` per plugin, keyed by plugin name. Each plugin's transformer runs in
|
|
1889
|
+
* isolation on the original adapter node. `applyTo` is a lookup, not a chain, so plugin A's
|
|
1890
|
+
* visitor never sees plugin B's output. When no transformer is registered, `applyTo` returns
|
|
1891
|
+
* the original node reference, and the `@kubb/ast` `transform` primitive does the same when
|
|
1892
|
+
* its visitor leaves the tree untouched. Callers can compare by identity to detect a no-op.
|
|
1893
|
+
*
|
|
1894
|
+
* Registration order matches the order setup hooks fire, which the driver has already sorted
|
|
1895
|
+
* by `enforce` and dependency edges. The registry does not re-order anything.
|
|
1896
|
+
*/
|
|
1897
|
+
var Transform = class {
|
|
1898
|
+
#visitors = /* @__PURE__ */ new Map();
|
|
1899
|
+
/**
|
|
1900
|
+
* Number of plugins with a registered transformer.
|
|
1901
|
+
*/
|
|
1902
|
+
get size() {
|
|
1903
|
+
return this.#visitors.size;
|
|
1904
|
+
}
|
|
1905
|
+
/**
|
|
1906
|
+
* Records `visitor` as the transformer for `pluginName`. A second call for the same plugin
|
|
1907
|
+
* replaces the first.
|
|
1908
|
+
*/
|
|
1909
|
+
register(pluginName, visitor) {
|
|
1910
|
+
this.#visitors.set(pluginName, visitor);
|
|
1911
|
+
}
|
|
1912
|
+
/**
|
|
1913
|
+
* Looks up the transformer for `pluginName`. The generator context uses this so plugins can
|
|
1914
|
+
* read their own visitor through `ctx.transformer`.
|
|
1915
|
+
*/
|
|
1916
|
+
get(pluginName) {
|
|
1917
|
+
return this.#visitors.get(pluginName);
|
|
1918
|
+
}
|
|
1919
|
+
/**
|
|
1920
|
+
* Runs the plugin's transformer on `node`. Returns the original node reference when the
|
|
1921
|
+
* plugin has no transformer, so callers can compare by identity to detect a no-op.
|
|
1922
|
+
*/
|
|
1923
|
+
applyTo(pluginName, node) {
|
|
1924
|
+
const visitor = this.#visitors.get(pluginName);
|
|
1925
|
+
if (!visitor) return node;
|
|
1926
|
+
return (0, _kubb_ast.transform)(node, visitor);
|
|
1927
|
+
}
|
|
1928
|
+
/**
|
|
1929
|
+
* Clears every registration. Called from the driver's `dispose()` so visitors do not leak
|
|
1930
|
+
* across builds.
|
|
1931
|
+
*/
|
|
1932
|
+
dispose() {
|
|
1933
|
+
this.#visitors.clear();
|
|
1934
|
+
}
|
|
1935
|
+
};
|
|
1936
|
+
//#endregion
|
|
1318
1937
|
//#region \0@oxc-project+runtime@0.133.0/helpers/esm/usingCtx.js
|
|
1319
1938
|
function _usingCtx() {
|
|
1320
1939
|
var r = "function" == typeof SuppressedError ? SuppressedError : function(r, e) {
|
|
@@ -1379,13 +1998,6 @@ function _usingCtx() {
|
|
|
1379
1998
|
function enforceOrder(enforce) {
|
|
1380
1999
|
return enforce === "pre" ? -1 : enforce === "post" ? 1 : 0;
|
|
1381
2000
|
}
|
|
1382
|
-
const OPERATION_FILTER_TYPES = new Set([
|
|
1383
|
-
"tag",
|
|
1384
|
-
"operationId",
|
|
1385
|
-
"path",
|
|
1386
|
-
"method",
|
|
1387
|
-
"contentType"
|
|
1388
|
-
]);
|
|
1389
2001
|
var KubbDriver = class KubbDriver {
|
|
1390
2002
|
config;
|
|
1391
2003
|
options;
|
|
@@ -1403,7 +2015,7 @@ var KubbDriver = class KubbDriver {
|
|
|
1403
2015
|
}
|
|
1404
2016
|
/**
|
|
1405
2017
|
* The streaming `InputStreamNode` produced by the adapter.
|
|
1406
|
-
* Always set after adapter setup
|
|
2018
|
+
* Always set after adapter setup, parse-only adapters are wrapped automatically.
|
|
1407
2019
|
*/
|
|
1408
2020
|
inputNode = null;
|
|
1409
2021
|
adapter = null;
|
|
@@ -1411,7 +2023,7 @@ var KubbDriver = class KubbDriver {
|
|
|
1411
2023
|
* Studio session state, kept together so `dispose()` can reset it atomically.
|
|
1412
2024
|
*
|
|
1413
2025
|
* - `source` holds the raw adapter source so `adapter.parse()` can be called lazily.
|
|
1414
|
-
* Intentionally outlives the build
|
|
2026
|
+
* Intentionally outlives the build, cleared by `dispose()`.
|
|
1415
2027
|
* - `isOpen` prevents opening the studio more than once per build.
|
|
1416
2028
|
* - `inputNode` caches the parse promise so `adapter.parse()` is called at most once
|
|
1417
2029
|
* per studio session, even when `openInStudio()` is called multiple times.
|
|
@@ -1421,14 +2033,12 @@ var KubbDriver = class KubbDriver {
|
|
|
1421
2033
|
isOpen: false,
|
|
1422
2034
|
inputNode: null
|
|
1423
2035
|
};
|
|
1424
|
-
#middlewareListeners = [];
|
|
1425
2036
|
/**
|
|
1426
2037
|
* Central file store for all generated files.
|
|
1427
2038
|
* Plugins should use `this.addFile()` / `this.upsertFile()` (via their context) to
|
|
1428
|
-
* add files
|
|
2039
|
+
* add files. This property gives direct read/write access when needed.
|
|
1429
2040
|
*/
|
|
1430
2041
|
fileManager = new FileManager();
|
|
1431
|
-
#fileProcessor = new FileProcessor();
|
|
1432
2042
|
plugins = /* @__PURE__ */ new Map();
|
|
1433
2043
|
/**
|
|
1434
2044
|
* Tracks which plugins have generators registered via `addGenerator()` (event-based path).
|
|
@@ -1437,11 +2047,22 @@ var KubbDriver = class KubbDriver {
|
|
|
1437
2047
|
#eventGeneratorPlugins = /* @__PURE__ */ new Set();
|
|
1438
2048
|
#resolvers = /* @__PURE__ */ new Map();
|
|
1439
2049
|
#defaultResolvers = /* @__PURE__ */ new Map();
|
|
1440
|
-
|
|
2050
|
+
/**
|
|
2051
|
+
* Tracks every listener the driver added (plugin, middleware, generator) so `dispose()` can
|
|
2052
|
+
* remove them in one pass. Middleware registers after plugins, so it fires last via `Set`
|
|
2053
|
+
* insertion order. External `hooks.on(...)` listeners are not tracked.
|
|
2054
|
+
*/
|
|
2055
|
+
#registry;
|
|
2056
|
+
/**
|
|
2057
|
+
* Transform registry. Plugins populate it during `kubb:plugin:setup` via `setTransformer`,
|
|
2058
|
+
* and `#runGenerators` reads it once per `(plugin, node)` pair through `applyTo`.
|
|
2059
|
+
*/
|
|
2060
|
+
#transforms = new Transform();
|
|
1441
2061
|
constructor(config, options) {
|
|
1442
2062
|
this.config = config;
|
|
1443
2063
|
this.options = options;
|
|
1444
2064
|
this.adapter = config.adapter ?? null;
|
|
2065
|
+
this.#registry = new HookRegistry({ emitter: options.hooks });
|
|
1445
2066
|
}
|
|
1446
2067
|
async setup() {
|
|
1447
2068
|
const normalized = this.config.plugins.map((rawPlugin) => this.#normalizePlugin(rawPlugin));
|
|
@@ -1456,7 +2077,7 @@ var KubbDriver = class KubbDriver {
|
|
|
1456
2077
|
this.plugins.set(plugin.name, plugin);
|
|
1457
2078
|
}
|
|
1458
2079
|
if (this.config.middleware) for (const middleware of this.config.middleware) for (const event of Object.keys(middleware.hooks)) this.#registerMiddleware(event, middleware.hooks);
|
|
1459
|
-
if (this.config.adapter)
|
|
2080
|
+
if (this.config.adapter) this.#studio.source = inputToAdapterSource(this.config);
|
|
1460
2081
|
}
|
|
1461
2082
|
get hooks() {
|
|
1462
2083
|
return this.options.hooks;
|
|
@@ -1480,46 +2101,39 @@ var KubbDriver = class KubbDriver {
|
|
|
1480
2101
|
if ("apply" in plugin && typeof plugin.apply === "function") normalized.apply = plugin.apply;
|
|
1481
2102
|
return normalized;
|
|
1482
2103
|
}
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
2104
|
+
/**
|
|
2105
|
+
* Parses the adapter source into `this.inputNode`. Idempotent, so repeated calls from
|
|
2106
|
+
* `run` or the studio path do not re-parse. Adapters with `stream()` are used directly.
|
|
2107
|
+
* Adapters with only `parse()` are wrapped via `createStreamInput` so the dispatch loop
|
|
2108
|
+
* stays stream-only.
|
|
2109
|
+
*/
|
|
2110
|
+
async #parseInput() {
|
|
2111
|
+
if (this.inputNode || !this.adapter || !this.#studio.source) return;
|
|
2112
|
+
const adapter = this.adapter;
|
|
2113
|
+
const source = this.#studio.source;
|
|
1486
2114
|
if (adapter.stream) {
|
|
1487
2115
|
this.inputNode = await adapter.stream(source);
|
|
1488
|
-
|
|
1489
|
-
date: /* @__PURE__ */ new Date(),
|
|
1490
|
-
logs: [`✓ Adapter '${adapter.name}' producing input stream`]
|
|
1491
|
-
});
|
|
1492
|
-
} else {
|
|
1493
|
-
const inputNode = await adapter.parse(source);
|
|
1494
|
-
this.inputNode = (0, _kubb_ast.createStreamInput)(arrayToAsyncIterable(inputNode.schemas), arrayToAsyncIterable(inputNode.operations), inputNode.meta);
|
|
1495
|
-
await this.hooks.emit("kubb:debug", {
|
|
1496
|
-
date: /* @__PURE__ */ new Date(),
|
|
1497
|
-
logs: [
|
|
1498
|
-
`✓ Adapter '${adapter.name}' resolved InputNode (wrapped as stream)`,
|
|
1499
|
-
` • Schemas: ${inputNode.schemas.length}`,
|
|
1500
|
-
` • Operations: ${inputNode.operations.length}`
|
|
1501
|
-
]
|
|
1502
|
-
});
|
|
2116
|
+
return;
|
|
1503
2117
|
}
|
|
2118
|
+
const parsed = await adapter.parse(source);
|
|
2119
|
+
this.inputNode = (0, _kubb_ast.createStreamInput)(arrayToAsyncIterable(parsed.schemas), arrayToAsyncIterable(parsed.operations), parsed.meta);
|
|
1504
2120
|
}
|
|
1505
2121
|
#registerMiddleware(event, middlewareHooks) {
|
|
1506
2122
|
const handler = middlewareHooks[event];
|
|
1507
2123
|
if (!handler) return;
|
|
1508
|
-
this.
|
|
1509
|
-
|
|
2124
|
+
this.#registry.register({
|
|
2125
|
+
event,
|
|
2126
|
+
handler,
|
|
2127
|
+
source: "middleware"
|
|
2128
|
+
});
|
|
1510
2129
|
}
|
|
1511
2130
|
/**
|
|
1512
2131
|
* Registers a hook-style plugin's lifecycle handlers on the shared `AsyncEventEmitter`.
|
|
1513
2132
|
*
|
|
1514
|
-
*
|
|
1515
|
-
*
|
|
1516
|
-
* `
|
|
1517
|
-
*
|
|
1518
|
-
* All other hooks are iterated and registered directly as pass-through listeners.
|
|
1519
|
-
* Any event key present in the global `KubbHooks` interface can be subscribed to.
|
|
1520
|
-
*
|
|
1521
|
-
* External tooling can subscribe to any of these events via `hooks.on(...)` to observe
|
|
1522
|
-
* the plugin lifecycle without modifying plugin behavior.
|
|
2133
|
+
* The `kubb:plugin:setup` listener wraps the global context in a plugin-specific one so
|
|
2134
|
+
* `addGenerator`, `setResolver`, and `setTransformer` target the right `normalizedPlugin`.
|
|
2135
|
+
* Every other `KubbHooks` event registers as a pass-through listener that external tooling
|
|
2136
|
+
* can observe via `hooks.on(...)`.
|
|
1523
2137
|
*
|
|
1524
2138
|
* @internal
|
|
1525
2139
|
*/
|
|
@@ -1538,10 +2152,7 @@ var KubbDriver = class KubbDriver {
|
|
|
1538
2152
|
this.setPluginResolver(plugin.name, resolver);
|
|
1539
2153
|
},
|
|
1540
2154
|
setTransformer: (visitor) => {
|
|
1541
|
-
plugin.
|
|
1542
|
-
},
|
|
1543
|
-
setRenderer: (renderer) => {
|
|
1544
|
-
plugin.renderer = renderer;
|
|
2155
|
+
this.#transforms.register(plugin.name, visitor);
|
|
1545
2156
|
},
|
|
1546
2157
|
setOptions: (opts) => {
|
|
1547
2158
|
plugin.options = {
|
|
@@ -1555,13 +2166,21 @@ var KubbDriver = class KubbDriver {
|
|
|
1555
2166
|
};
|
|
1556
2167
|
return hooks["kubb:plugin:setup"](pluginCtx);
|
|
1557
2168
|
};
|
|
1558
|
-
this.
|
|
1559
|
-
|
|
2169
|
+
this.#registry.register({
|
|
2170
|
+
event: "kubb:plugin:setup",
|
|
2171
|
+
handler: setupHandler,
|
|
2172
|
+
source: "plugin"
|
|
2173
|
+
});
|
|
1560
2174
|
}
|
|
1561
|
-
for (const
|
|
1562
|
-
if (event === "kubb:plugin:setup"
|
|
1563
|
-
|
|
1564
|
-
|
|
2175
|
+
for (const event of Object.keys(hooks)) {
|
|
2176
|
+
if (event === "kubb:plugin:setup") continue;
|
|
2177
|
+
const handler = hooks[event];
|
|
2178
|
+
if (!handler) continue;
|
|
2179
|
+
this.#registry.register({
|
|
2180
|
+
event,
|
|
2181
|
+
handler,
|
|
2182
|
+
source: "plugin"
|
|
2183
|
+
});
|
|
1565
2184
|
}
|
|
1566
2185
|
}
|
|
1567
2186
|
/**
|
|
@@ -1578,7 +2197,6 @@ var KubbDriver = class KubbDriver {
|
|
|
1578
2197
|
addGenerator: noop,
|
|
1579
2198
|
setResolver: noop,
|
|
1580
2199
|
setTransformer: noop,
|
|
1581
|
-
setRenderer: noop,
|
|
1582
2200
|
setOptions: noop,
|
|
1583
2201
|
injectFile: noop,
|
|
1584
2202
|
updateConfig: noop
|
|
@@ -1592,52 +2210,56 @@ var KubbDriver = class KubbDriver {
|
|
|
1592
2210
|
* respectively. Each listener is scoped to the owning plugin via a `ctx.plugin.name` check
|
|
1593
2211
|
* so that generators from different plugins do not cross-fire.
|
|
1594
2212
|
*
|
|
1595
|
-
* The renderer
|
|
1596
|
-
*
|
|
1597
|
-
* declares a renderer.
|
|
2213
|
+
* The renderer comes from `generator.renderer`. Set `generator.renderer = null` (or leave it
|
|
2214
|
+
* unset) to opt out of rendering.
|
|
1598
2215
|
*
|
|
1599
2216
|
* Call this method inside `addGenerator()` (in `kubb:plugin:setup`) to wire up a generator.
|
|
1600
2217
|
*/
|
|
1601
|
-
registerGenerator(pluginName,
|
|
1602
|
-
|
|
1603
|
-
const plugin = this.plugins.get(pluginName);
|
|
1604
|
-
return gen.renderer === null ? void 0 : gen.renderer ?? plugin?.renderer ?? this.config.renderer;
|
|
1605
|
-
};
|
|
1606
|
-
if (gen.schema) {
|
|
2218
|
+
registerGenerator(pluginName, generator) {
|
|
2219
|
+
if (generator.schema) {
|
|
1607
2220
|
const schemaHandler = async (node, ctx) => {
|
|
1608
2221
|
if (ctx.plugin.name !== pluginName) return;
|
|
1609
|
-
await
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
2222
|
+
const result = await generator.schema(node, ctx);
|
|
2223
|
+
await this.dispatch({
|
|
2224
|
+
result,
|
|
2225
|
+
renderer: generator.renderer
|
|
1613
2226
|
});
|
|
1614
2227
|
};
|
|
1615
|
-
this.
|
|
1616
|
-
|
|
2228
|
+
this.#registry.register({
|
|
2229
|
+
event: "kubb:generate:schema",
|
|
2230
|
+
handler: schemaHandler,
|
|
2231
|
+
source: "driver"
|
|
2232
|
+
});
|
|
1617
2233
|
}
|
|
1618
|
-
if (
|
|
2234
|
+
if (generator.operation) {
|
|
1619
2235
|
const operationHandler = async (node, ctx) => {
|
|
1620
2236
|
if (ctx.plugin.name !== pluginName) return;
|
|
1621
|
-
await
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
2237
|
+
const result = await generator.operation(node, ctx);
|
|
2238
|
+
await this.dispatch({
|
|
2239
|
+
result,
|
|
2240
|
+
renderer: generator.renderer
|
|
1625
2241
|
});
|
|
1626
2242
|
};
|
|
1627
|
-
this.
|
|
1628
|
-
|
|
2243
|
+
this.#registry.register({
|
|
2244
|
+
event: "kubb:generate:operation",
|
|
2245
|
+
handler: operationHandler,
|
|
2246
|
+
source: "driver"
|
|
2247
|
+
});
|
|
1629
2248
|
}
|
|
1630
|
-
if (
|
|
2249
|
+
if (generator.operations) {
|
|
1631
2250
|
const operationsHandler = async (nodes, ctx) => {
|
|
1632
2251
|
if (ctx.plugin.name !== pluginName) return;
|
|
1633
|
-
await
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
2252
|
+
const result = await generator.operations(nodes, ctx);
|
|
2253
|
+
await this.dispatch({
|
|
2254
|
+
result,
|
|
2255
|
+
renderer: generator.renderer
|
|
1637
2256
|
});
|
|
1638
2257
|
};
|
|
1639
|
-
this.
|
|
1640
|
-
|
|
2258
|
+
this.#registry.register({
|
|
2259
|
+
event: "kubb:generate:operations",
|
|
2260
|
+
handler: operationsHandler,
|
|
2261
|
+
source: "driver"
|
|
2262
|
+
});
|
|
1641
2263
|
}
|
|
1642
2264
|
this.#eventGeneratorPlugins.add(pluginName);
|
|
1643
2265
|
}
|
|
@@ -1652,143 +2274,110 @@ var KubbDriver = class KubbDriver {
|
|
|
1652
2274
|
return this.#eventGeneratorPlugins.has(pluginName);
|
|
1653
2275
|
}
|
|
1654
2276
|
/**
|
|
1655
|
-
* Runs the full plugin pipeline. Returns
|
|
1656
|
-
* when an outer hook throws
|
|
1657
|
-
* the
|
|
2277
|
+
* Runs the full plugin pipeline. Returns the diagnostics collected so far even
|
|
2278
|
+
* when an outer hook throws, since the orchestrator preserves partial state by capturing
|
|
2279
|
+
* the failure as a {@link Diagnostic} instead of propagating. Each plugin also
|
|
2280
|
+
* contributes a `timing` diagnostic for the run summary.
|
|
1658
2281
|
*/
|
|
1659
2282
|
async run({ storage }) {
|
|
1660
|
-
const hooks = this
|
|
1661
|
-
const
|
|
1662
|
-
const failedPlugins = /* @__PURE__ */ new Set();
|
|
1663
|
-
const pluginTimings = /* @__PURE__ */ new Map();
|
|
2283
|
+
const { hooks, config } = this;
|
|
2284
|
+
const diagnostics = [];
|
|
1664
2285
|
const parsersMap = /* @__PURE__ */ new Map();
|
|
1665
2286
|
for (const parser of config.parsers) if (parser.extNames) for (const ext of parser.extNames) parsersMap.set(ext, parser);
|
|
1666
|
-
const
|
|
1667
|
-
|
|
1668
|
-
|
|
2287
|
+
const processor = new FileProcessor({
|
|
2288
|
+
parsers: parsersMap,
|
|
2289
|
+
storage,
|
|
2290
|
+
extension: config.output.extension
|
|
1669
2291
|
});
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
2292
|
+
processor.hooks.on("start", async (files) => {
|
|
2293
|
+
await hooks.emit("kubb:files:processing:start", { files });
|
|
2294
|
+
});
|
|
2295
|
+
const updateBuffer = [];
|
|
2296
|
+
processor.hooks.on("update", (item) => {
|
|
2297
|
+
updateBuffer.push(item);
|
|
2298
|
+
});
|
|
2299
|
+
processor.hooks.on("end", async (files) => {
|
|
2300
|
+
await hooks.emit("kubb:files:processing:update", { files: updateBuffer.map((item) => ({
|
|
2301
|
+
...item,
|
|
2302
|
+
config
|
|
2303
|
+
})) });
|
|
2304
|
+
updateBuffer.length = 0;
|
|
2305
|
+
await hooks.emit("kubb:files:processing:end", { files });
|
|
2306
|
+
});
|
|
2307
|
+
const onFileUpsert = (file) => {
|
|
2308
|
+
processor.enqueue(file);
|
|
2309
|
+
};
|
|
2310
|
+
this.fileManager.hooks.on("upsert", onFileUpsert);
|
|
2311
|
+
return Diagnostics.scope((diagnostic) => diagnostics.push(diagnostic), async () => {
|
|
2312
|
+
try {
|
|
2313
|
+
await this.#parseInput();
|
|
2314
|
+
await this.emitSetupHooks();
|
|
2315
|
+
if (this.adapter && this.inputNode) await hooks.emit("kubb:build:start", Object.assign({
|
|
2316
|
+
config,
|
|
2317
|
+
adapter: this.adapter,
|
|
2318
|
+
meta: this.inputNode.meta,
|
|
2319
|
+
getPlugin: this.getPlugin.bind(this)
|
|
2320
|
+
}, this.#filesPayload()));
|
|
2321
|
+
const generatorPlugins = [];
|
|
2322
|
+
for (const plugin of this.plugins.values()) {
|
|
2323
|
+
const context = this.getContext(plugin);
|
|
2324
|
+
const hrStart = process.hrtime();
|
|
2325
|
+
try {
|
|
2326
|
+
await hooks.emit("kubb:plugin:start", { plugin });
|
|
2327
|
+
} catch (caughtError) {
|
|
2328
|
+
const error = caughtError;
|
|
2329
|
+
const duration = getElapsedMs(hrStart);
|
|
2330
|
+
await this.#emitPluginEnd({
|
|
2331
|
+
plugin,
|
|
2332
|
+
duration,
|
|
2333
|
+
success: false,
|
|
2334
|
+
error
|
|
2335
|
+
});
|
|
2336
|
+
diagnostics.push({
|
|
2337
|
+
...Diagnostics.from(error),
|
|
2338
|
+
plugin: plugin.name
|
|
2339
|
+
}, Diagnostics.performance({
|
|
2340
|
+
plugin: plugin.name,
|
|
2341
|
+
duration
|
|
2342
|
+
}));
|
|
2343
|
+
continue;
|
|
2344
|
+
}
|
|
2345
|
+
if (this.hasEventGenerators(plugin.name)) {
|
|
2346
|
+
generatorPlugins.push({
|
|
2347
|
+
plugin,
|
|
2348
|
+
context,
|
|
2349
|
+
hrStart
|
|
2350
|
+
});
|
|
2351
|
+
continue;
|
|
2352
|
+
}
|
|
1723
2353
|
const duration = getElapsedMs(hrStart);
|
|
1724
|
-
|
|
2354
|
+
diagnostics.push(Diagnostics.performance({
|
|
2355
|
+
plugin: plugin.name,
|
|
2356
|
+
duration
|
|
2357
|
+
}));
|
|
1725
2358
|
await this.#emitPluginEnd({
|
|
1726
2359
|
plugin,
|
|
1727
2360
|
duration,
|
|
1728
|
-
success:
|
|
1729
|
-
error
|
|
1730
|
-
});
|
|
1731
|
-
failedPlugins.add({
|
|
1732
|
-
plugin,
|
|
1733
|
-
error
|
|
1734
|
-
});
|
|
1735
|
-
continue;
|
|
1736
|
-
}
|
|
1737
|
-
if (plugin.generators?.length || this.hasEventGenerators(plugin.name)) {
|
|
1738
|
-
generatorPlugins.push({
|
|
1739
|
-
plugin,
|
|
1740
|
-
context,
|
|
1741
|
-
hrStart
|
|
2361
|
+
success: true
|
|
1742
2362
|
});
|
|
1743
|
-
continue;
|
|
1744
2363
|
}
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
await this.#
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
date: /* @__PURE__ */ new Date(),
|
|
1754
|
-
logs: [`✓ Plugin started successfully (${formatMs(duration)})`]
|
|
1755
|
-
});
|
|
1756
|
-
}
|
|
1757
|
-
if (generatorPlugins.length > 0) if (this.inputNode) {
|
|
1758
|
-
const { timings, failed } = await this.#runGenerators(generatorPlugins, flushPending);
|
|
1759
|
-
await flushPending();
|
|
1760
|
-
for (const [name, duration] of timings) pluginTimings.set(name, duration);
|
|
1761
|
-
for (const entry of failed) failedPlugins.add(entry);
|
|
1762
|
-
} else for (const { plugin, hrStart } of generatorPlugins) {
|
|
1763
|
-
const duration = getElapsedMs(hrStart);
|
|
1764
|
-
pluginTimings.set(plugin.name, duration);
|
|
1765
|
-
await this.#emitPluginEnd({
|
|
1766
|
-
plugin,
|
|
1767
|
-
duration,
|
|
1768
|
-
success: true
|
|
2364
|
+
diagnostics.push(...await this.#runGenerators(generatorPlugins, () => processor.flush()));
|
|
2365
|
+
await processor.drain();
|
|
2366
|
+
await hooks.emit("kubb:plugins:end", Object.assign({ config }, this.#filesPayload()));
|
|
2367
|
+
await processor.drain();
|
|
2368
|
+
await hooks.emit("kubb:build:end", {
|
|
2369
|
+
files: this.fileManager.files,
|
|
2370
|
+
config,
|
|
2371
|
+
outputDir: (0, node_path.resolve)(config.root, config.output.path)
|
|
1769
2372
|
});
|
|
2373
|
+
return { diagnostics: Diagnostics.dedupe(diagnostics) };
|
|
2374
|
+
} catch (caughtError) {
|
|
2375
|
+
diagnostics.push(Diagnostics.from(caughtError));
|
|
2376
|
+
return { diagnostics: Diagnostics.dedupe(diagnostics) };
|
|
2377
|
+
} finally {
|
|
2378
|
+
this.fileManager.hooks.off("upsert", onFileUpsert);
|
|
1770
2379
|
}
|
|
1771
|
-
|
|
1772
|
-
await flushPending();
|
|
1773
|
-
const files = this.fileManager.files;
|
|
1774
|
-
await hooks.emit("kubb:build:end", {
|
|
1775
|
-
files,
|
|
1776
|
-
config,
|
|
1777
|
-
outputDir: (0, node_path.resolve)(config.root, config.output.path)
|
|
1778
|
-
});
|
|
1779
|
-
return {
|
|
1780
|
-
failedPlugins,
|
|
1781
|
-
pluginTimings
|
|
1782
|
-
};
|
|
1783
|
-
} catch (caughtError) {
|
|
1784
|
-
return {
|
|
1785
|
-
failedPlugins,
|
|
1786
|
-
pluginTimings,
|
|
1787
|
-
error: caughtError
|
|
1788
|
-
};
|
|
1789
|
-
} finally {
|
|
1790
|
-
this.fileManager.setOnUpsert(null);
|
|
1791
|
-
}
|
|
2380
|
+
});
|
|
1792
2381
|
}
|
|
1793
2382
|
#filesPayload() {
|
|
1794
2383
|
const driver = this;
|
|
@@ -1808,10 +2397,36 @@ var KubbDriver = class KubbDriver {
|
|
|
1808
2397
|
config: this.config
|
|
1809
2398
|
}, this.#filesPayload()));
|
|
1810
2399
|
}
|
|
2400
|
+
/**
|
|
2401
|
+
* Streams schemas and operations through every plugin's generators. Each node is run
|
|
2402
|
+
* through the plugin's transformer (from `this.#transforms`) before the generator sees it,
|
|
2403
|
+
* so plugins stay isolated and the hot path stays per-node. Schemas run before operations
|
|
2404
|
+
* because the two passes share `flushPending` and the FileProcessor's event emitter.
|
|
2405
|
+
* A failing plugin contributes an error diagnostic so the rest of the build continues.
|
|
2406
|
+
* Every plugin also contributes a `timing` diagnostic.
|
|
2407
|
+
*
|
|
2408
|
+
* When `entries` is empty or `this.inputNode` is `null`, every entry still gets a
|
|
2409
|
+
* `kubb:plugin:end` so middleware listeners (the barrel writer and friends) complete.
|
|
2410
|
+
*/
|
|
1811
2411
|
async #runGenerators(entries, flushPending) {
|
|
1812
|
-
const
|
|
1813
|
-
|
|
1814
|
-
|
|
2412
|
+
const diagnostics = [];
|
|
2413
|
+
if (entries.length === 0) return diagnostics;
|
|
2414
|
+
if (!this.inputNode) {
|
|
2415
|
+
for (const { plugin, hrStart } of entries) {
|
|
2416
|
+
const duration = getElapsedMs(hrStart);
|
|
2417
|
+
diagnostics.push(Diagnostics.performance({
|
|
2418
|
+
plugin: plugin.name,
|
|
2419
|
+
duration
|
|
2420
|
+
}));
|
|
2421
|
+
await this.#emitPluginEnd({
|
|
2422
|
+
plugin,
|
|
2423
|
+
duration,
|
|
2424
|
+
success: true
|
|
2425
|
+
});
|
|
2426
|
+
}
|
|
2427
|
+
return diagnostics;
|
|
2428
|
+
}
|
|
2429
|
+
const transforms = this.#transforms;
|
|
1815
2430
|
const { schemas, operations } = this.inputNode;
|
|
1816
2431
|
const states = entries.map(({ plugin, context, hrStart }) => {
|
|
1817
2432
|
const { exclude, include, override } = plugin.options;
|
|
@@ -1841,7 +2456,7 @@ var KubbDriver = class KubbDriver {
|
|
|
1841
2456
|
if (pruningStates.length > 0) {
|
|
1842
2457
|
const allSchemas = [];
|
|
1843
2458
|
for await (const schema of schemas) allSchemas.push(schema);
|
|
1844
|
-
const includedOpsByState = new Map(pruningStates.map((
|
|
2459
|
+
const includedOpsByState = new Map(pruningStates.map((state) => [state, []]));
|
|
1845
2460
|
for await (const operation of operations) for (const state of pruningStates) {
|
|
1846
2461
|
const { exclude, include, override } = state.plugin.options;
|
|
1847
2462
|
if (state.generatorContext.resolver.resolveOptions(operation, {
|
|
@@ -1856,33 +2471,45 @@ var KubbDriver = class KubbDriver {
|
|
|
1856
2471
|
includedOpsByState.delete(state);
|
|
1857
2472
|
}
|
|
1858
2473
|
}
|
|
1859
|
-
const
|
|
2474
|
+
const resolveForPlugin = (state, node) => {
|
|
2475
|
+
const { plugin, generatorContext } = state;
|
|
2476
|
+
const transformedNode = transforms.applyTo(plugin.name, node);
|
|
2477
|
+
if (state.optionsAreStatic) return {
|
|
2478
|
+
transformedNode,
|
|
2479
|
+
options: plugin.options
|
|
2480
|
+
};
|
|
2481
|
+
const { exclude, include, override } = plugin.options;
|
|
2482
|
+
const options = generatorContext.resolver.resolveOptions(transformedNode, {
|
|
2483
|
+
options: plugin.options,
|
|
2484
|
+
exclude,
|
|
2485
|
+
include,
|
|
2486
|
+
override
|
|
2487
|
+
});
|
|
2488
|
+
if (options === null) return null;
|
|
2489
|
+
return {
|
|
2490
|
+
transformedNode,
|
|
2491
|
+
options
|
|
2492
|
+
};
|
|
2493
|
+
};
|
|
1860
2494
|
const dispatchNode = async (state, node, dispatch) => {
|
|
1861
2495
|
if (state.failed) return;
|
|
1862
2496
|
try {
|
|
1863
|
-
const
|
|
1864
|
-
|
|
2497
|
+
const resolved = resolveForPlugin(state, node);
|
|
2498
|
+
if (!resolved) return;
|
|
2499
|
+
const { transformedNode, options } = resolved;
|
|
1865
2500
|
if (dispatch.checkAllowedNames && state.allowedSchemaNames !== null && "name" in transformedNode && transformedNode.name && !state.allowedSchemaNames.has(transformedNode.name)) return;
|
|
1866
|
-
const { exclude, include, override } = plugin.options;
|
|
1867
|
-
const options = state.optionsAreStatic ? plugin.options : generatorContext.resolver.resolveOptions(transformedNode, {
|
|
1868
|
-
options: plugin.options,
|
|
1869
|
-
exclude,
|
|
1870
|
-
include,
|
|
1871
|
-
override
|
|
1872
|
-
});
|
|
1873
|
-
if (options === null) return;
|
|
1874
2501
|
const ctx = {
|
|
1875
|
-
...generatorContext,
|
|
2502
|
+
...state.generatorContext,
|
|
1876
2503
|
options
|
|
1877
2504
|
};
|
|
1878
|
-
for (const gen of generators) {
|
|
1879
|
-
const
|
|
1880
|
-
if (!
|
|
1881
|
-
const raw =
|
|
1882
|
-
const
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
2505
|
+
for (const gen of state.generators) {
|
|
2506
|
+
const run = gen[dispatch.method];
|
|
2507
|
+
if (!run) continue;
|
|
2508
|
+
const raw = run(transformedNode, ctx);
|
|
2509
|
+
const result = isPromise(raw) ? await raw : raw;
|
|
2510
|
+
const applied = this.dispatch({
|
|
2511
|
+
result,
|
|
2512
|
+
renderer: gen.renderer
|
|
1886
2513
|
});
|
|
1887
2514
|
if (isPromise(applied)) await applied;
|
|
1888
2515
|
}
|
|
@@ -1902,15 +2529,15 @@ var KubbDriver = class KubbDriver {
|
|
|
1902
2529
|
checkAllowedNames: false,
|
|
1903
2530
|
emit: emitsOperationHook ? (node, ctx) => this.hooks.emit("kubb:generate:operation", node, ctx) : null
|
|
1904
2531
|
};
|
|
1905
|
-
const needsCollectedOperations = this.hooks.listenerCount("kubb:generate:operations") > 0 || states.some((
|
|
2532
|
+
const needsCollectedOperations = this.hooks.listenerCount("kubb:generate:operations") > 0 || states.some((state) => state.generators.some((gen) => !!gen.operations));
|
|
1906
2533
|
const collectedOperations = needsCollectedOperations ? [] : void 0;
|
|
1907
|
-
await forBatches(schemas, (nodes) => Promise.all(nodes.flatMap((
|
|
2534
|
+
await forBatches(schemas, (nodes) => Promise.all(nodes.flatMap((node) => states.map((state) => dispatchNode(state, node, schemaDispatch)))), {
|
|
1908
2535
|
concurrency: 8,
|
|
1909
2536
|
flush: flushPending
|
|
1910
2537
|
});
|
|
1911
2538
|
await forBatches(operations, (nodes) => {
|
|
1912
2539
|
if (needsCollectedOperations) collectedOperations?.push(...nodes);
|
|
1913
|
-
return Promise.all(nodes.flatMap((
|
|
2540
|
+
return Promise.all(nodes.flatMap((node) => states.map((state) => dispatchNode(state, node, operationDispatch))));
|
|
1914
2541
|
}, {
|
|
1915
2542
|
concurrency: 8,
|
|
1916
2543
|
flush: flushPending
|
|
@@ -1922,23 +2549,17 @@ var KubbDriver = class KubbDriver {
|
|
|
1922
2549
|
...generatorContext,
|
|
1923
2550
|
options: plugin.options
|
|
1924
2551
|
};
|
|
1925
|
-
const
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
options: plugin.options,
|
|
1931
|
-
exclude,
|
|
1932
|
-
include,
|
|
1933
|
-
override
|
|
1934
|
-
}) !== null;
|
|
1935
|
-
});
|
|
2552
|
+
const pluginOperations = (collectedOperations ?? []).reduce((acc, node) => {
|
|
2553
|
+
const resolved = resolveForPlugin(state, node);
|
|
2554
|
+
if (resolved) acc.push(resolved.transformedNode);
|
|
2555
|
+
return acc;
|
|
2556
|
+
}, []);
|
|
1936
2557
|
for (const gen of generators) {
|
|
1937
2558
|
if (!gen.operations) continue;
|
|
1938
|
-
await
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
|
|
2559
|
+
const result = await gen.operations(pluginOperations, ctx);
|
|
2560
|
+
await this.dispatch({
|
|
2561
|
+
result,
|
|
2562
|
+
renderer: gen.renderer
|
|
1942
2563
|
});
|
|
1943
2564
|
}
|
|
1944
2565
|
await this.hooks.emit("kubb:generate:operations", pluginOperations, ctx);
|
|
@@ -1947,60 +2568,80 @@ var KubbDriver = class KubbDriver {
|
|
|
1947
2568
|
state.error = caughtError;
|
|
1948
2569
|
}
|
|
1949
2570
|
const duration = getElapsedMs(state.hrStart);
|
|
1950
|
-
timings.set(state.plugin.name, duration);
|
|
1951
2571
|
await this.#emitPluginEnd({
|
|
1952
2572
|
plugin: state.plugin,
|
|
1953
2573
|
duration,
|
|
1954
2574
|
success: !state.failed,
|
|
1955
2575
|
error: state.failed && state.error ? state.error : void 0
|
|
1956
2576
|
});
|
|
1957
|
-
if (state.failed && state.error)
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
});
|
|
1961
|
-
await this.hooks.emit("kubb:debug", {
|
|
1962
|
-
date: /* @__PURE__ */ new Date(),
|
|
1963
|
-
logs: [state.failed ? "✗ Plugin start failed" : `✓ Plugin started successfully (${formatMs(duration)})`]
|
|
2577
|
+
if (state.failed && state.error) diagnostics.push({
|
|
2578
|
+
...Diagnostics.from(state.error),
|
|
2579
|
+
plugin: state.plugin.name
|
|
1964
2580
|
});
|
|
2581
|
+
diagnostics.push(Diagnostics.performance({
|
|
2582
|
+
plugin: state.plugin.name,
|
|
2583
|
+
duration
|
|
2584
|
+
}));
|
|
1965
2585
|
}
|
|
1966
|
-
return
|
|
1967
|
-
timings,
|
|
1968
|
-
failed
|
|
1969
|
-
};
|
|
2586
|
+
return diagnostics;
|
|
1970
2587
|
}
|
|
1971
2588
|
/**
|
|
1972
|
-
*
|
|
1973
|
-
*
|
|
2589
|
+
* Stores whatever a generator method or `kubb:generate:*` hook returned.
|
|
2590
|
+
*
|
|
2591
|
+
* - An `Array<FileNode>` goes straight into `fileManager` via `upsert`.
|
|
2592
|
+
* - A renderer element runs through `renderer` (the renderer factory, e.g. JSX) and the
|
|
2593
|
+
* produced files go to `fileManager.upsert`.
|
|
2594
|
+
* - A falsy result is treated as a no-op. The generator wrote files itself via
|
|
2595
|
+
* `ctx.upsertFile`.
|
|
2596
|
+
*
|
|
2597
|
+
* Pass `renderer` when the result may be a renderer element. Generators that only return
|
|
2598
|
+
* `Array<FileNode>` do not need one.
|
|
2599
|
+
*/
|
|
2600
|
+
async dispatch({ result, renderer }) {
|
|
2601
|
+
try {
|
|
2602
|
+
var _usingCtx$1 = _usingCtx();
|
|
2603
|
+
if (!result) return;
|
|
2604
|
+
if (Array.isArray(result)) {
|
|
2605
|
+
this.fileManager.upsert(...result);
|
|
2606
|
+
return;
|
|
2607
|
+
}
|
|
2608
|
+
if (!renderer) return;
|
|
2609
|
+
const instance = _usingCtx$1.u(renderer());
|
|
2610
|
+
if (instance.stream) {
|
|
2611
|
+
for (const file of instance.stream(result)) this.fileManager.upsert(file);
|
|
2612
|
+
return;
|
|
2613
|
+
}
|
|
2614
|
+
await instance.render(result);
|
|
2615
|
+
this.fileManager.upsert(...instance.files);
|
|
2616
|
+
} catch (_) {
|
|
2617
|
+
_usingCtx$1.e = _;
|
|
2618
|
+
} finally {
|
|
2619
|
+
_usingCtx$1.d();
|
|
2620
|
+
}
|
|
2621
|
+
}
|
|
2622
|
+
/**
|
|
2623
|
+
* Removes every listener the driver added. Listeners attached directly to `hooks` from outside
|
|
2624
|
+
* the driver survive. Called at the end of a build to prevent leaks across repeated builds.
|
|
1974
2625
|
*
|
|
1975
2626
|
* @internal
|
|
1976
2627
|
*/
|
|
1977
2628
|
dispose() {
|
|
1978
|
-
|
|
1979
|
-
this.#hookListeners.clear();
|
|
2629
|
+
this.#registry.dispose();
|
|
1980
2630
|
this.#eventGeneratorPlugins.clear();
|
|
2631
|
+
this.#transforms.dispose();
|
|
1981
2632
|
this.#resolvers.clear();
|
|
1982
2633
|
this.#defaultResolvers.clear();
|
|
1983
2634
|
this.fileManager.dispose();
|
|
1984
|
-
this.#fileProcessor.dispose();
|
|
1985
2635
|
this.inputNode = null;
|
|
1986
2636
|
this.#studio = {
|
|
1987
2637
|
source: null,
|
|
1988
2638
|
isOpen: false,
|
|
1989
2639
|
inputNode: null
|
|
1990
2640
|
};
|
|
1991
|
-
for (const [event, handler] of this.#middlewareListeners) this.hooks.off(event, handler);
|
|
1992
2641
|
}
|
|
1993
2642
|
[Symbol.dispose]() {
|
|
1994
2643
|
this.dispose();
|
|
1995
2644
|
}
|
|
1996
|
-
#trackHookListener(event, handler) {
|
|
1997
|
-
let handlers = this.#hookListeners.get(event);
|
|
1998
|
-
if (!handlers) {
|
|
1999
|
-
handlers = /* @__PURE__ */ new Set();
|
|
2000
|
-
this.#hookListeners.set(event, handlers);
|
|
2001
|
-
}
|
|
2002
|
-
handlers.add(handler);
|
|
2003
|
-
}
|
|
2004
2645
|
#getDefaultResolver = memoize(this.#defaultResolvers, (pluginName) => defineResolver(() => ({
|
|
2005
2646
|
name: "default",
|
|
2006
2647
|
pluginName
|
|
@@ -2024,6 +2665,12 @@ var KubbDriver = class KubbDriver {
|
|
|
2024
2665
|
}
|
|
2025
2666
|
getContext(plugin) {
|
|
2026
2667
|
const driver = this;
|
|
2668
|
+
const report = (diagnostic) => {
|
|
2669
|
+
Diagnostics.report({
|
|
2670
|
+
...diagnostic,
|
|
2671
|
+
plugin: plugin.name
|
|
2672
|
+
});
|
|
2673
|
+
};
|
|
2027
2674
|
return {
|
|
2028
2675
|
config: driver.config,
|
|
2029
2676
|
get root() {
|
|
@@ -2057,21 +2704,47 @@ var KubbDriver = class KubbDriver {
|
|
|
2057
2704
|
return driver.getResolver(plugin.name);
|
|
2058
2705
|
},
|
|
2059
2706
|
get transformer() {
|
|
2060
|
-
return plugin.
|
|
2707
|
+
return driver.#transforms.get(plugin.name);
|
|
2061
2708
|
},
|
|
2062
2709
|
warn(message) {
|
|
2063
|
-
|
|
2710
|
+
report({
|
|
2711
|
+
code: diagnosticCode.pluginWarning,
|
|
2712
|
+
severity: "warning",
|
|
2713
|
+
message
|
|
2714
|
+
});
|
|
2064
2715
|
},
|
|
2065
2716
|
error(error) {
|
|
2066
|
-
|
|
2717
|
+
const cause = typeof error === "string" ? void 0 : error;
|
|
2718
|
+
report({
|
|
2719
|
+
code: diagnosticCode.pluginFailed,
|
|
2720
|
+
severity: "error",
|
|
2721
|
+
message: typeof error === "string" ? error : error.message,
|
|
2722
|
+
cause
|
|
2723
|
+
});
|
|
2067
2724
|
},
|
|
2068
2725
|
info(message) {
|
|
2069
|
-
|
|
2726
|
+
report({
|
|
2727
|
+
code: diagnosticCode.pluginInfo,
|
|
2728
|
+
severity: "info",
|
|
2729
|
+
message
|
|
2730
|
+
});
|
|
2070
2731
|
},
|
|
2071
2732
|
async openInStudio(options) {
|
|
2072
2733
|
if (!driver.config.devtools || driver.#studio.isOpen) return;
|
|
2073
|
-
if (typeof driver.config.devtools !== "object") throw new
|
|
2074
|
-
|
|
2734
|
+
if (typeof driver.config.devtools !== "object") throw new DiagnosticError({
|
|
2735
|
+
code: diagnosticCode.devtoolsInvalid,
|
|
2736
|
+
severity: "error",
|
|
2737
|
+
message: "The `devtools` config must be an object.",
|
|
2738
|
+
help: "Set `devtools` to an options object, or remove it to disable Kubb Studio.",
|
|
2739
|
+
location: { kind: "config" }
|
|
2740
|
+
});
|
|
2741
|
+
if (!driver.adapter || !driver.#studio.source) throw new DiagnosticError({
|
|
2742
|
+
code: diagnosticCode.adapterRequired,
|
|
2743
|
+
severity: "error",
|
|
2744
|
+
message: "An adapter is required to open Kubb Studio, but none is configured.",
|
|
2745
|
+
help: "Set `adapter` in kubb.config.ts (for example `adapterOas()`).",
|
|
2746
|
+
location: { kind: "config" }
|
|
2747
|
+
});
|
|
2075
2748
|
driver.#studio.isOpen = true;
|
|
2076
2749
|
const studioUrl = driver.config.devtools?.studioUrl ?? "https://kubb.studio";
|
|
2077
2750
|
driver.#studio.inputNode ??= Promise.resolve(driver.adapter.parse(driver.#studio.source));
|
|
@@ -2084,59 +2757,25 @@ var KubbDriver = class KubbDriver {
|
|
|
2084
2757
|
}
|
|
2085
2758
|
requirePlugin(pluginName) {
|
|
2086
2759
|
const plugin = this.plugins.get(pluginName);
|
|
2087
|
-
if (!plugin) throw new
|
|
2760
|
+
if (!plugin) throw new DiagnosticError({
|
|
2761
|
+
code: diagnosticCode.pluginNotFound,
|
|
2762
|
+
severity: "error",
|
|
2763
|
+
message: `Plugin "${pluginName}" is required but not found. Make sure it is included in your Kubb config.`,
|
|
2764
|
+
help: `Add "${pluginName}" to the \`plugins\` array in kubb.config.ts, or remove the dependency on it.`,
|
|
2765
|
+
location: { kind: "config" }
|
|
2766
|
+
});
|
|
2088
2767
|
return plugin;
|
|
2089
2768
|
}
|
|
2090
2769
|
};
|
|
2091
|
-
/**
|
|
2092
|
-
* Handles the return value of a plugin AST hook or generator method.
|
|
2093
|
-
*
|
|
2094
|
-
* - Renderer output → rendered via the provided `rendererFactory` (e.g. JSX), files stored in `driver.fileManager`
|
|
2095
|
-
* - `Array<FileNode>` → added directly into `driver.fileManager`
|
|
2096
|
-
* - `void` / `null` / `undefined` → no-op (plugin handled it via `this.upsertFile`)
|
|
2097
|
-
*
|
|
2098
|
-
* Pass a `rendererFactory` (e.g. `jsxRenderer` from `@kubb/renderer-jsx`) when the result
|
|
2099
|
-
* may be a renderer element. Generators that only return `Array<FileNode>` do not need one.
|
|
2100
|
-
*/
|
|
2101
|
-
function applyHookResult({ result, driver, rendererFactory }) {
|
|
2102
|
-
if (!result) return;
|
|
2103
|
-
if (Array.isArray(result)) {
|
|
2104
|
-
driver.fileManager.upsert(...result);
|
|
2105
|
-
return;
|
|
2106
|
-
}
|
|
2107
|
-
if (!rendererFactory) return;
|
|
2108
|
-
const renderer = rendererFactory();
|
|
2109
|
-
if (renderer.stream) try {
|
|
2110
|
-
var _usingCtx$1 = _usingCtx();
|
|
2111
|
-
const r = _usingCtx$1.u(renderer);
|
|
2112
|
-
for (const file of r.stream(result)) driver.fileManager.upsert(file);
|
|
2113
|
-
return;
|
|
2114
|
-
} catch (_) {
|
|
2115
|
-
_usingCtx$1.e = _;
|
|
2116
|
-
} finally {
|
|
2117
|
-
_usingCtx$1.d();
|
|
2118
|
-
}
|
|
2119
|
-
return applyAsyncRender({
|
|
2120
|
-
renderer,
|
|
2121
|
-
result,
|
|
2122
|
-
driver
|
|
2123
|
-
});
|
|
2124
|
-
}
|
|
2125
|
-
async function applyAsyncRender({ renderer, result, driver }) {
|
|
2126
|
-
try {
|
|
2127
|
-
var _usingCtx3 = _usingCtx();
|
|
2128
|
-
const r = _usingCtx3.u(renderer);
|
|
2129
|
-
await r.render(result);
|
|
2130
|
-
driver.fileManager.upsert(...r.files);
|
|
2131
|
-
} catch (_) {
|
|
2132
|
-
_usingCtx3.e = _;
|
|
2133
|
-
} finally {
|
|
2134
|
-
_usingCtx3.d();
|
|
2135
|
-
}
|
|
2136
|
-
}
|
|
2137
2770
|
function inputToAdapterSource(config) {
|
|
2138
2771
|
const input = config.input;
|
|
2139
|
-
if (!input) throw new
|
|
2772
|
+
if (!input) throw new DiagnosticError({
|
|
2773
|
+
code: diagnosticCode.inputRequired,
|
|
2774
|
+
severity: "error",
|
|
2775
|
+
message: "An adapter is configured without an input.",
|
|
2776
|
+
help: "Provide `input.path` (a file or URL) or `input.data` (an inline spec) in your Kubb config.",
|
|
2777
|
+
location: { kind: "config" }
|
|
2778
|
+
});
|
|
2140
2779
|
if ("data" in input) return {
|
|
2141
2780
|
type: "data",
|
|
2142
2781
|
data: input.data
|
|
@@ -2163,22 +2802,22 @@ Object.defineProperty(exports, "BuildError", {
|
|
|
2163
2802
|
return BuildError;
|
|
2164
2803
|
}
|
|
2165
2804
|
});
|
|
2166
|
-
Object.defineProperty(exports, "
|
|
2805
|
+
Object.defineProperty(exports, "DEFAULT_STUDIO_URL", {
|
|
2167
2806
|
enumerable: true,
|
|
2168
2807
|
get: function() {
|
|
2169
|
-
return
|
|
2808
|
+
return DEFAULT_STUDIO_URL;
|
|
2170
2809
|
}
|
|
2171
2810
|
});
|
|
2172
|
-
Object.defineProperty(exports, "
|
|
2811
|
+
Object.defineProperty(exports, "DiagnosticError", {
|
|
2173
2812
|
enumerable: true,
|
|
2174
2813
|
get: function() {
|
|
2175
|
-
return
|
|
2814
|
+
return DiagnosticError;
|
|
2176
2815
|
}
|
|
2177
2816
|
});
|
|
2178
|
-
Object.defineProperty(exports, "
|
|
2817
|
+
Object.defineProperty(exports, "Diagnostics", {
|
|
2179
2818
|
enumerable: true,
|
|
2180
2819
|
get: function() {
|
|
2181
|
-
return
|
|
2820
|
+
return Diagnostics;
|
|
2182
2821
|
}
|
|
2183
2822
|
});
|
|
2184
2823
|
Object.defineProperty(exports, "FileManager", {
|
|
@@ -2223,12 +2862,6 @@ Object.defineProperty(exports, "_usingCtx", {
|
|
|
2223
2862
|
return _usingCtx;
|
|
2224
2863
|
}
|
|
2225
2864
|
});
|
|
2226
|
-
Object.defineProperty(exports, "applyHookResult", {
|
|
2227
|
-
enumerable: true,
|
|
2228
|
-
get: function() {
|
|
2229
|
-
return applyHookResult;
|
|
2230
|
-
}
|
|
2231
|
-
});
|
|
2232
2865
|
Object.defineProperty(exports, "definePlugin", {
|
|
2233
2866
|
enumerable: true,
|
|
2234
2867
|
get: function() {
|
|
@@ -2241,11 +2874,47 @@ Object.defineProperty(exports, "defineResolver", {
|
|
|
2241
2874
|
return defineResolver;
|
|
2242
2875
|
}
|
|
2243
2876
|
});
|
|
2877
|
+
Object.defineProperty(exports, "diagnosticCatalog", {
|
|
2878
|
+
enumerable: true,
|
|
2879
|
+
get: function() {
|
|
2880
|
+
return diagnosticCatalog;
|
|
2881
|
+
}
|
|
2882
|
+
});
|
|
2883
|
+
Object.defineProperty(exports, "diagnosticCode", {
|
|
2884
|
+
enumerable: true,
|
|
2885
|
+
get: function() {
|
|
2886
|
+
return diagnosticCode;
|
|
2887
|
+
}
|
|
2888
|
+
});
|
|
2889
|
+
Object.defineProperty(exports, "isPerformanceDiagnostic", {
|
|
2890
|
+
enumerable: true,
|
|
2891
|
+
get: function() {
|
|
2892
|
+
return isPerformanceDiagnostic;
|
|
2893
|
+
}
|
|
2894
|
+
});
|
|
2895
|
+
Object.defineProperty(exports, "isProblemDiagnostic", {
|
|
2896
|
+
enumerable: true,
|
|
2897
|
+
get: function() {
|
|
2898
|
+
return isProblemDiagnostic;
|
|
2899
|
+
}
|
|
2900
|
+
});
|
|
2901
|
+
Object.defineProperty(exports, "isUpdateDiagnostic", {
|
|
2902
|
+
enumerable: true,
|
|
2903
|
+
get: function() {
|
|
2904
|
+
return isUpdateDiagnostic;
|
|
2905
|
+
}
|
|
2906
|
+
});
|
|
2244
2907
|
Object.defineProperty(exports, "logLevel", {
|
|
2245
2908
|
enumerable: true,
|
|
2246
2909
|
get: function() {
|
|
2247
2910
|
return logLevel;
|
|
2248
2911
|
}
|
|
2249
2912
|
});
|
|
2913
|
+
Object.defineProperty(exports, "narrowDiagnostic", {
|
|
2914
|
+
enumerable: true,
|
|
2915
|
+
get: function() {
|
|
2916
|
+
return narrowDiagnostic;
|
|
2917
|
+
}
|
|
2918
|
+
});
|
|
2250
2919
|
|
|
2251
|
-
//# sourceMappingURL=KubbDriver-
|
|
2920
|
+
//# sourceMappingURL=KubbDriver-BYBUfOZ8.cjs.map
|