@kubb/core 5.0.0-beta.35 → 5.0.0-beta.37
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-T8W1wm4O.cjs → KubbDriver-CXoKVRxI.cjs} +1091 -413
- package/dist/KubbDriver-CXoKVRxI.cjs.map +1 -0
- package/dist/{KubbDriver-7JLUadUM.js → KubbDriver-CckeYpMG.js} +1050 -402
- package/dist/KubbDriver-CckeYpMG.js.map +1 -0
- package/dist/{createKubb-BKpcUB6g.d.ts → diagnostics-B0ONXReg.d.ts} +790 -287
- package/dist/index.cjs +48 -89
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +40 -90
- package/dist/index.js.map +1 -1
- package/dist/mocks.cjs +23 -23
- package/dist/mocks.cjs.map +1 -1
- package/dist/mocks.d.ts +2 -2
- package/dist/mocks.js +23 -23
- 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 +333 -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 +84 -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 +5 -5
- package/src/types.ts +16 -4
- package/dist/KubbDriver-7JLUadUM.js.map +0 -1
- package/dist/KubbDriver-T8W1wm4O.cjs.map +0 -1
|
@@ -2,6 +2,7 @@ import "./chunk-C0LytTxp.js";
|
|
|
2
2
|
import { EventEmitter } from "node:events";
|
|
3
3
|
import path, { extname, resolve } from "node:path";
|
|
4
4
|
import { collectUsedSchemaNames, createFile, createStreamInput, extractStringsFromNodes, isOperationNode, isSchemaNode, transform } from "@kubb/ast";
|
|
5
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
5
6
|
import { deflateSync } from "fflate";
|
|
6
7
|
import { x } from "tinyexec";
|
|
7
8
|
//#region ../../internals/utils/src/errors.ts
|
|
@@ -36,6 +37,18 @@ var BuildError = class extends Error {
|
|
|
36
37
|
function toError(value) {
|
|
37
38
|
return value instanceof Error ? value : new Error(String(value));
|
|
38
39
|
}
|
|
40
|
+
/**
|
|
41
|
+
* Extracts a human-readable message from any thrown value.
|
|
42
|
+
*
|
|
43
|
+
* @example
|
|
44
|
+
* ```ts
|
|
45
|
+
* getErrorMessage(new Error('oops')) // 'oops'
|
|
46
|
+
* getErrorMessage('plain string') // 'plain string'
|
|
47
|
+
* ```
|
|
48
|
+
*/
|
|
49
|
+
function getErrorMessage(value) {
|
|
50
|
+
return value instanceof Error ? value.message : String(value);
|
|
51
|
+
}
|
|
39
52
|
//#endregion
|
|
40
53
|
//#region ../../internals/utils/src/asyncEventEmitter.ts
|
|
41
54
|
/**
|
|
@@ -135,6 +148,24 @@ var AsyncEventEmitter = class {
|
|
|
135
148
|
return this.#emitter.listenerCount(eventName);
|
|
136
149
|
}
|
|
137
150
|
/**
|
|
151
|
+
* Raises or lowers the per-event listener ceiling before Node warns about a memory leak.
|
|
152
|
+
* Set this above the expected listener count when many listeners attach by design.
|
|
153
|
+
*
|
|
154
|
+
* @example
|
|
155
|
+
* ```ts
|
|
156
|
+
* emitter.setMaxListeners(40)
|
|
157
|
+
* ```
|
|
158
|
+
*/
|
|
159
|
+
setMaxListeners(max) {
|
|
160
|
+
this.#emitter.setMaxListeners(max);
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Returns the current per-event listener ceiling.
|
|
164
|
+
*/
|
|
165
|
+
getMaxListeners() {
|
|
166
|
+
return this.#emitter.getMaxListeners();
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
138
169
|
* Removes all listeners from every event channel.
|
|
139
170
|
*
|
|
140
171
|
* @example
|
|
@@ -228,21 +259,6 @@ function getElapsedMs(hrStart) {
|
|
|
228
259
|
const ms = seconds * 1e3 + nanoseconds / 1e6;
|
|
229
260
|
return Math.round(ms * 100) / 100;
|
|
230
261
|
}
|
|
231
|
-
/**
|
|
232
|
-
* Converts a millisecond duration into a human-readable string (`ms`, `s`, or `m s`).
|
|
233
|
-
*
|
|
234
|
-
* @example
|
|
235
|
-
* ```ts
|
|
236
|
-
* formatMs(250) // '250ms'
|
|
237
|
-
* formatMs(1500) // '1.50s'
|
|
238
|
-
* formatMs(90000) // '1m 30.0s'
|
|
239
|
-
* ```
|
|
240
|
-
*/
|
|
241
|
-
function formatMs(ms) {
|
|
242
|
-
if (ms >= 6e4) return `${Math.floor(ms / 6e4)}m ${(ms % 6e4 / 1e3).toFixed(1)}s`;
|
|
243
|
-
if (ms >= 1e3) return `${(ms / 1e3).toFixed(2)}s`;
|
|
244
|
-
return `${Math.round(ms)}ms`;
|
|
245
|
-
}
|
|
246
262
|
//#endregion
|
|
247
263
|
//#region ../../internals/utils/src/promise.ts
|
|
248
264
|
function* chunks(arr, size) {
|
|
@@ -610,13 +626,17 @@ var URLPath = class {
|
|
|
610
626
|
*/
|
|
611
627
|
const DEFAULT_STUDIO_URL = "https://kubb.studio";
|
|
612
628
|
/**
|
|
613
|
-
*
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
/**
|
|
617
|
-
* Default file-extension mapping used when no explicit mapping is configured.
|
|
629
|
+
* Plugin `include` filter types that select operations directly. When one of these is set
|
|
630
|
+
* without a `schemaName` include, the generate phase pre-scans operations to compute the set
|
|
631
|
+
* of schemas they reach, so unreachable schemas can be pruned for that plugin.
|
|
618
632
|
*/
|
|
619
|
-
const
|
|
633
|
+
const OPERATION_FILTER_TYPES = new Set([
|
|
634
|
+
"tag",
|
|
635
|
+
"operationId",
|
|
636
|
+
"path",
|
|
637
|
+
"method",
|
|
638
|
+
"contentType"
|
|
639
|
+
]);
|
|
620
640
|
/**
|
|
621
641
|
* Numeric log-level thresholds used internally to compare verbosity.
|
|
622
642
|
*
|
|
@@ -627,8 +647,453 @@ const logLevel = {
|
|
|
627
647
|
error: 0,
|
|
628
648
|
warn: 1,
|
|
629
649
|
info: 3,
|
|
630
|
-
verbose: 4
|
|
631
|
-
|
|
650
|
+
verbose: 4
|
|
651
|
+
};
|
|
652
|
+
/**
|
|
653
|
+
* Stable codes Kubb attaches to a `Diagnostic`. Each maps to a known failure mode
|
|
654
|
+
* and stays stable so it can be referenced in tooling and (later) docs. Reference
|
|
655
|
+
* these instead of inlining the string at a throw site.
|
|
656
|
+
*/
|
|
657
|
+
const diagnosticCode = {
|
|
658
|
+
/**
|
|
659
|
+
* Fallback for an unstructured error with no specific code.
|
|
660
|
+
*/
|
|
661
|
+
unknown: "KUBB_UNKNOWN",
|
|
662
|
+
/**
|
|
663
|
+
* The `input.path` file or URL could not be read.
|
|
664
|
+
*/
|
|
665
|
+
inputNotFound: "KUBB_INPUT_NOT_FOUND",
|
|
666
|
+
/**
|
|
667
|
+
* An adapter was configured without an `input`.
|
|
668
|
+
*/
|
|
669
|
+
inputRequired: "KUBB_INPUT_REQUIRED",
|
|
670
|
+
/**
|
|
671
|
+
* A `$ref` (or equivalent reference) could not be resolved in the source document.
|
|
672
|
+
*/
|
|
673
|
+
refNotFound: "KUBB_REF_NOT_FOUND",
|
|
674
|
+
/**
|
|
675
|
+
* A server variable value is not allowed by its `enum`.
|
|
676
|
+
*/
|
|
677
|
+
invalidServerVariable: "KUBB_INVALID_SERVER_VARIABLE",
|
|
678
|
+
/**
|
|
679
|
+
* A required plugin is missing from the config.
|
|
680
|
+
*/
|
|
681
|
+
pluginNotFound: "KUBB_PLUGIN_NOT_FOUND",
|
|
682
|
+
/**
|
|
683
|
+
* A plugin threw while generating.
|
|
684
|
+
*/
|
|
685
|
+
pluginFailed: "KUBB_PLUGIN_FAILED",
|
|
686
|
+
/**
|
|
687
|
+
* A plugin reported a non-fatal warning through `ctx.warn`.
|
|
688
|
+
*/
|
|
689
|
+
pluginWarning: "KUBB_PLUGIN_WARNING",
|
|
690
|
+
/**
|
|
691
|
+
* A plugin reported an informational message through `ctx.info`.
|
|
692
|
+
*/
|
|
693
|
+
pluginInfo: "KUBB_PLUGIN_INFO",
|
|
694
|
+
/**
|
|
695
|
+
* A schema uses a `format` Kubb does not map to a specific type. Reserved for
|
|
696
|
+
* adapters to emit as a `warning`.
|
|
697
|
+
*/
|
|
698
|
+
unsupportedFormat: "KUBB_UNSUPPORTED_FORMAT",
|
|
699
|
+
/**
|
|
700
|
+
* A referenced schema or operation is marked `deprecated`. Reserved for adapters
|
|
701
|
+
* to emit as an `info`.
|
|
702
|
+
*/
|
|
703
|
+
deprecated: "KUBB_DEPRECATED",
|
|
704
|
+
/**
|
|
705
|
+
* An adapter is required but the config has none. The build cannot read the input
|
|
706
|
+
* without one.
|
|
707
|
+
*/
|
|
708
|
+
adapterRequired: "KUBB_ADAPTER_REQUIRED",
|
|
709
|
+
/**
|
|
710
|
+
* The `devtools` config is set to something other than an object.
|
|
711
|
+
*/
|
|
712
|
+
devtoolsInvalid: "KUBB_DEVTOOLS_INVALID",
|
|
713
|
+
/**
|
|
714
|
+
* A resolved output path escapes the output directory, which can stem from a path
|
|
715
|
+
* traversal in the spec or a misconfigured `group.name`.
|
|
716
|
+
*/
|
|
717
|
+
pathTraversal: "KUBB_PATH_TRAVERSAL",
|
|
718
|
+
/**
|
|
719
|
+
* A post-generate shell hook (`hooks.done`) exited with a failure.
|
|
720
|
+
*/
|
|
721
|
+
hookFailed: "KUBB_HOOK_FAILED",
|
|
722
|
+
/**
|
|
723
|
+
* The formatter pass over the generated files failed.
|
|
724
|
+
*/
|
|
725
|
+
formatFailed: "KUBB_FORMAT_FAILED",
|
|
726
|
+
/**
|
|
727
|
+
* The linter pass over the generated files failed.
|
|
728
|
+
*/
|
|
729
|
+
lintFailed: "KUBB_LINT_FAILED",
|
|
730
|
+
/**
|
|
731
|
+
* Not a failure. Carries a plugin's elapsed time, summed into the run total.
|
|
732
|
+
*/
|
|
733
|
+
performance: "KUBB_PERFORMANCE",
|
|
734
|
+
/**
|
|
735
|
+
* Not a failure. A newer Kubb version is available on npm.
|
|
736
|
+
*/
|
|
737
|
+
updateAvailable: "KUBB_UPDATE_AVAILABLE"
|
|
738
|
+
};
|
|
739
|
+
//#endregion
|
|
740
|
+
//#region src/diagnostics.ts
|
|
741
|
+
/**
|
|
742
|
+
* Docs major, derived from the package version so the link tracks the published major.
|
|
743
|
+
*/
|
|
744
|
+
const docsMajor = "5.0.0-beta.37".split(".")[0] ?? "5";
|
|
745
|
+
/**
|
|
746
|
+
* Narrows a {@link Diagnostic} to the variant for `code`, or `null` when it does not match.
|
|
747
|
+
*
|
|
748
|
+
* @example
|
|
749
|
+
* ```ts
|
|
750
|
+
* const update = narrowDiagnostic(diagnostic, diagnosticCode.updateAvailable)
|
|
751
|
+
* if (update) {
|
|
752
|
+
* console.log(update.latestVersion)
|
|
753
|
+
* }
|
|
754
|
+
* ```
|
|
755
|
+
*/
|
|
756
|
+
function narrowDiagnostic(diagnostic, code) {
|
|
757
|
+
return diagnostic.code === code ? diagnostic : null;
|
|
758
|
+
}
|
|
759
|
+
/**
|
|
760
|
+
* Builds a type guard that narrows a {@link Diagnostic} to the variant for `kind`. A diagnostic
|
|
761
|
+
* with no `kind` is treated as a `problem`.
|
|
762
|
+
*/
|
|
763
|
+
function isKind(kind) {
|
|
764
|
+
return (diagnostic) => (diagnostic.kind ?? "problem") === kind;
|
|
765
|
+
}
|
|
766
|
+
/**
|
|
767
|
+
* Returns `true` when the diagnostic is a build {@link ProblemDiagnostic}.
|
|
768
|
+
*
|
|
769
|
+
* @example
|
|
770
|
+
* ```ts
|
|
771
|
+
* if (isProblemDiagnostic(diagnostic)) {
|
|
772
|
+
* console.log(diagnostic.location)
|
|
773
|
+
* }
|
|
774
|
+
* ```
|
|
775
|
+
*/
|
|
776
|
+
const isProblemDiagnostic = isKind("problem");
|
|
777
|
+
/**
|
|
778
|
+
* Returns `true` when the diagnostic is a per-plugin {@link PerformanceDiagnostic}.
|
|
779
|
+
*
|
|
780
|
+
* @example
|
|
781
|
+
* ```ts
|
|
782
|
+
* const timings = diagnostics.filter(isPerformanceDiagnostic)
|
|
783
|
+
* ```
|
|
784
|
+
*/
|
|
785
|
+
const isPerformanceDiagnostic = isKind("performance");
|
|
786
|
+
/**
|
|
787
|
+
* Returns `true` when the diagnostic is a version-update {@link UpdateDiagnostic}.
|
|
788
|
+
*
|
|
789
|
+
* @example
|
|
790
|
+
* ```ts
|
|
791
|
+
* if (isUpdateDiagnostic(diagnostic)) {
|
|
792
|
+
* console.log(diagnostic.latestVersion)
|
|
793
|
+
* }
|
|
794
|
+
* ```
|
|
795
|
+
*/
|
|
796
|
+
const isUpdateDiagnostic = isKind("update");
|
|
797
|
+
/**
|
|
798
|
+
* An `Error` that carries a {@link Diagnostic}, so structured problems can flow
|
|
799
|
+
* through the existing throw/catch paths while keeping their code and location.
|
|
800
|
+
*
|
|
801
|
+
* @example
|
|
802
|
+
* ```ts
|
|
803
|
+
* throw new DiagnosticError({ code: diagnosticCode.refNotFound, severity: 'error', message: `Could not find ${ref}`, location: { kind: 'schema', pointer: ref, ref } })
|
|
804
|
+
* ```
|
|
805
|
+
*/
|
|
806
|
+
var DiagnosticError = class extends Error {
|
|
807
|
+
diagnostic;
|
|
808
|
+
constructor(diagnostic) {
|
|
809
|
+
super(diagnostic.message, { cause: diagnostic.cause });
|
|
810
|
+
this.name = "DiagnosticError";
|
|
811
|
+
this.diagnostic = diagnostic;
|
|
812
|
+
}
|
|
813
|
+
};
|
|
814
|
+
/**
|
|
815
|
+
* Structural check for a {@link DiagnosticError}, including one thrown from a duplicated
|
|
816
|
+
* `@kubb/core` copy where `instanceof` fails. Matches on the `name` and a `diagnostic`
|
|
817
|
+
* that carries a `code`.
|
|
818
|
+
*/
|
|
819
|
+
function isDiagnosticError(error) {
|
|
820
|
+
if (error instanceof DiagnosticError) return true;
|
|
821
|
+
return error instanceof Error && error.name === "DiagnosticError" && "diagnostic" in error && typeof error.diagnostic === "object" && error.diagnostic !== null && typeof error.diagnostic?.code === "string";
|
|
822
|
+
}
|
|
823
|
+
/**
|
|
824
|
+
* Explanation for every {@link diagnosticCode}. Use {@link Diagnostics.explain} to look one up
|
|
825
|
+
* and `Diagnostics.docsUrl` for the matching kubb.dev page.
|
|
826
|
+
*/
|
|
827
|
+
const diagnosticCatalog = {
|
|
828
|
+
[diagnosticCode.unknown]: {
|
|
829
|
+
title: "Unknown error",
|
|
830
|
+
cause: "An error was thrown without a stable Kubb code, so it is reported as-is.",
|
|
831
|
+
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."
|
|
832
|
+
},
|
|
833
|
+
[diagnosticCode.inputNotFound]: {
|
|
834
|
+
title: "Input not found",
|
|
835
|
+
cause: "The file or URL set in `input.path` (or passed as `kubb generate PATH`) could not be read.",
|
|
836
|
+
fix: "Check that the path or URL exists and is readable, then set it in `input.path` or pass it on the CLI."
|
|
837
|
+
},
|
|
838
|
+
[diagnosticCode.inputRequired]: {
|
|
839
|
+
title: "Input required",
|
|
840
|
+
cause: "An adapter is configured but no `input` was provided.",
|
|
841
|
+
fix: "Set `input.path` (a file or URL) or `input.data` (an inline spec) in your Kubb config."
|
|
842
|
+
},
|
|
843
|
+
[diagnosticCode.refNotFound]: {
|
|
844
|
+
title: "Reference not found",
|
|
845
|
+
cause: "A `$ref` could not be resolved in the source document.",
|
|
846
|
+
fix: "Add the missing definition (for example under `components.schemas`) or fix the `$ref`. Run `kubb validate` to check the spec."
|
|
847
|
+
},
|
|
848
|
+
[diagnosticCode.invalidServerVariable]: {
|
|
849
|
+
title: "Invalid server variable",
|
|
850
|
+
cause: "A server variable value is not allowed by its `enum`.",
|
|
851
|
+
fix: "Use one of the values listed in the server variable `enum`, or update the spec."
|
|
852
|
+
},
|
|
853
|
+
[diagnosticCode.pluginNotFound]: {
|
|
854
|
+
title: "Plugin not found",
|
|
855
|
+
cause: "A plugin that another plugin depends on is missing from the config.",
|
|
856
|
+
fix: "Add the required plugin to the `plugins` array in kubb.config.ts, or remove the dependency on it."
|
|
857
|
+
},
|
|
858
|
+
[diagnosticCode.pluginFailed]: {
|
|
859
|
+
title: "Plugin failed",
|
|
860
|
+
cause: "A plugin threw while generating, or reported an error through `ctx.error`.",
|
|
861
|
+
fix: "Read the underlying error and check the plugin options and the schema or operation it failed on."
|
|
862
|
+
},
|
|
863
|
+
[diagnosticCode.pluginWarning]: {
|
|
864
|
+
title: "Plugin warning",
|
|
865
|
+
cause: "A plugin reported a non-fatal warning through `ctx.warn`.",
|
|
866
|
+
fix: "Review the message. It does not fail the build; adjust the plugin options or input if the warning is unwanted."
|
|
867
|
+
},
|
|
868
|
+
[diagnosticCode.pluginInfo]: {
|
|
869
|
+
title: "Plugin info",
|
|
870
|
+
cause: "A plugin reported an informational message through `ctx.info`.",
|
|
871
|
+
fix: "Informational only. No action is required."
|
|
872
|
+
},
|
|
873
|
+
[diagnosticCode.unsupportedFormat]: {
|
|
874
|
+
title: "Unsupported format",
|
|
875
|
+
cause: "A schema uses a `format` Kubb does not map to a specific type, so it falls back to the base type.",
|
|
876
|
+
fix: "Use a format Kubb supports, or handle the custom format with a parser or plugin."
|
|
877
|
+
},
|
|
878
|
+
[diagnosticCode.deprecated]: {
|
|
879
|
+
title: "Deprecated",
|
|
880
|
+
cause: "A referenced schema or operation is marked `deprecated`.",
|
|
881
|
+
fix: "Migrate off the deprecated definition if the warning is unwanted."
|
|
882
|
+
},
|
|
883
|
+
[diagnosticCode.adapterRequired]: {
|
|
884
|
+
title: "Adapter required",
|
|
885
|
+
cause: "An action needs an adapter (for example opening Kubb Studio) but none is configured.",
|
|
886
|
+
fix: "Set `adapter` in kubb.config.ts, for example `adapterOas()`."
|
|
887
|
+
},
|
|
888
|
+
[diagnosticCode.devtoolsInvalid]: {
|
|
889
|
+
title: "Invalid devtools config",
|
|
890
|
+
cause: "The `devtools` config is set to something other than an object.",
|
|
891
|
+
fix: "Set `devtools` to an options object, or remove it to disable Kubb Studio."
|
|
892
|
+
},
|
|
893
|
+
[diagnosticCode.pathTraversal]: {
|
|
894
|
+
title: "Path traversal",
|
|
895
|
+
cause: "A resolved output path escaped the output directory, which can stem from a path traversal in the spec or a misconfigured `group.name`.",
|
|
896
|
+
fix: "Keep generated paths within the output directory. Review the `group.name` function and the names coming from the spec."
|
|
897
|
+
},
|
|
898
|
+
[diagnosticCode.hookFailed]: {
|
|
899
|
+
title: "Hook failed",
|
|
900
|
+
cause: "A post-generate shell hook (`hooks.done`) exited with a non-zero status.",
|
|
901
|
+
fix: "Check the command is installed and correct, and run it manually to see the error."
|
|
902
|
+
},
|
|
903
|
+
[diagnosticCode.formatFailed]: {
|
|
904
|
+
title: "Format failed",
|
|
905
|
+
cause: "The formatter pass over the generated files failed.",
|
|
906
|
+
fix: "Check the formatter (oxfmt, biome, or prettier) is installed and its config is valid, then run it manually on the output."
|
|
907
|
+
},
|
|
908
|
+
[diagnosticCode.lintFailed]: {
|
|
909
|
+
title: "Lint failed",
|
|
910
|
+
cause: "The linter pass over the generated files failed.",
|
|
911
|
+
fix: "Check the linter (oxlint, biome, or eslint) is installed and its config is valid, then run it manually on the output."
|
|
912
|
+
},
|
|
913
|
+
[diagnosticCode.performance]: {
|
|
914
|
+
title: "Performance",
|
|
915
|
+
cause: "Not a failure. Records a plugin’s elapsed time, summed into the run total.",
|
|
916
|
+
fix: "No action. This is an informational metric."
|
|
917
|
+
},
|
|
918
|
+
[diagnosticCode.updateAvailable]: {
|
|
919
|
+
title: "Update available",
|
|
920
|
+
cause: "A newer Kubb version is published on npm than the one running.",
|
|
921
|
+
fix: "Update the `@kubb/*` packages, for example `npm install -g @kubb/cli`, to get the latest fixes."
|
|
922
|
+
}
|
|
923
|
+
};
|
|
924
|
+
/**
|
|
925
|
+
* Static helpers for working with {@link Diagnostic}s, plus the run-scoped sink
|
|
926
|
+
* that lets deep code report a diagnostic without threading a callback.
|
|
927
|
+
*
|
|
928
|
+
* The sink lives in a single `AsyncLocalStorage` in the `@kubb/core` bundle.
|
|
929
|
+
* `Diagnostics.scope` activates it for a run, so anything inside that run (the
|
|
930
|
+
* adapter parse, a lazily consumed stream, a generator) reports through
|
|
931
|
+
* `Diagnostics.report` and lands in the same run.
|
|
932
|
+
*/
|
|
933
|
+
var Diagnostics = class Diagnostics {
|
|
934
|
+
static #reporterStorage = new AsyncLocalStorage();
|
|
935
|
+
/**
|
|
936
|
+
* Runs `fn` with `sink` as the active diagnostic sink for the whole async
|
|
937
|
+
* subtree, so {@link Diagnostics.report} reaches it from anywhere inside.
|
|
938
|
+
*/
|
|
939
|
+
static scope(sink, fn) {
|
|
940
|
+
return Diagnostics.#reporterStorage.run(sink, fn);
|
|
941
|
+
}
|
|
942
|
+
/**
|
|
943
|
+
* Collects a diagnostic into the active build via the run-scoped sink, without throwing.
|
|
944
|
+
* Returns `true` when a run consumed it, `false` when called outside a {@link Diagnostics.scope}
|
|
945
|
+
* (so callers can fall back to throwing). Use a `warning`/`info` severity for non-fatal issues.
|
|
946
|
+
* For rendering a diagnostic live on the hook bus, use {@link Diagnostics.emit} instead.
|
|
947
|
+
*/
|
|
948
|
+
static report(diagnostic) {
|
|
949
|
+
const sink = Diagnostics.#reporterStorage.getStore();
|
|
950
|
+
if (!sink) return false;
|
|
951
|
+
sink(diagnostic);
|
|
952
|
+
return true;
|
|
953
|
+
}
|
|
954
|
+
/**
|
|
955
|
+
* Emits a diagnostic on the run's `kubb:diagnostic` event so the loggers render it live.
|
|
956
|
+
* Use it instead of calling `hooks.emit('kubb:diagnostic', ...)` directly. To collect a
|
|
957
|
+
* diagnostic into the build result from deep in a run, use {@link Diagnostics.report} instead.
|
|
958
|
+
*/
|
|
959
|
+
static async emit(hooks, diagnostic) {
|
|
960
|
+
await hooks.emit("kubb:diagnostic", { diagnostic });
|
|
961
|
+
}
|
|
962
|
+
/**
|
|
963
|
+
* Coerces any thrown value into a {@link ProblemDiagnostic}. A {@link DiagnosticError}
|
|
964
|
+
* keeps its structured data, and anything else becomes a `KUBB_UNKNOWN` error.
|
|
965
|
+
*/
|
|
966
|
+
static from(error) {
|
|
967
|
+
const seen = /* @__PURE__ */ new Set();
|
|
968
|
+
let current = error;
|
|
969
|
+
let root;
|
|
970
|
+
while (current instanceof Error && !seen.has(current)) {
|
|
971
|
+
if (isDiagnosticError(current)) return current.diagnostic;
|
|
972
|
+
seen.add(current);
|
|
973
|
+
root = current;
|
|
974
|
+
current = current.cause;
|
|
975
|
+
}
|
|
976
|
+
return {
|
|
977
|
+
code: diagnosticCode.unknown,
|
|
978
|
+
severity: "error",
|
|
979
|
+
message: root ? root.message : getErrorMessage(error),
|
|
980
|
+
cause: root
|
|
981
|
+
};
|
|
982
|
+
}
|
|
983
|
+
/**
|
|
984
|
+
* Builds a per-plugin performance record. Reporters sum these into the run total.
|
|
985
|
+
*/
|
|
986
|
+
static performance({ plugin, duration }) {
|
|
987
|
+
return {
|
|
988
|
+
kind: "performance",
|
|
989
|
+
code: diagnosticCode.performance,
|
|
990
|
+
severity: "info",
|
|
991
|
+
message: `${plugin} generated in ${Math.round(duration)}ms`,
|
|
992
|
+
plugin,
|
|
993
|
+
duration
|
|
994
|
+
};
|
|
995
|
+
}
|
|
996
|
+
/**
|
|
997
|
+
* Builds the version-update notice shown when a newer Kubb is published on npm.
|
|
998
|
+
*/
|
|
999
|
+
static update({ currentVersion, latestVersion }) {
|
|
1000
|
+
return {
|
|
1001
|
+
kind: "update",
|
|
1002
|
+
code: diagnosticCode.updateAvailable,
|
|
1003
|
+
severity: "info",
|
|
1004
|
+
message: `Update available: v${currentVersion} → v${latestVersion}. Run \`npm install -g @kubb/cli\` to update.`,
|
|
1005
|
+
currentVersion,
|
|
1006
|
+
latestVersion
|
|
1007
|
+
};
|
|
1008
|
+
}
|
|
1009
|
+
/**
|
|
1010
|
+
* True when any diagnostic is an error, the severity that fails a build. Non-error
|
|
1011
|
+
* diagnostics are ignored.
|
|
1012
|
+
*/
|
|
1013
|
+
static hasError(diagnostics) {
|
|
1014
|
+
return diagnostics.some((diagnostic) => diagnostic.severity === "error");
|
|
1015
|
+
}
|
|
1016
|
+
/**
|
|
1017
|
+
* Names of the plugins that failed, deduped, derived from the error diagnostics
|
|
1018
|
+
* that carry a `plugin`.
|
|
1019
|
+
*/
|
|
1020
|
+
static failedPlugins(diagnostics) {
|
|
1021
|
+
const names = /* @__PURE__ */ new Set();
|
|
1022
|
+
for (const diagnostic of diagnostics) if (diagnostic.severity === "error" && diagnostic.plugin) names.add(diagnostic.plugin);
|
|
1023
|
+
return [...names];
|
|
1024
|
+
}
|
|
1025
|
+
/**
|
|
1026
|
+
* Counts `problem` diagnostics by severity for the run summary. `timing`
|
|
1027
|
+
* diagnostics are ignored.
|
|
1028
|
+
*/
|
|
1029
|
+
static count(diagnostics) {
|
|
1030
|
+
let errors = 0;
|
|
1031
|
+
let warnings = 0;
|
|
1032
|
+
let infos = 0;
|
|
1033
|
+
for (const diagnostic of diagnostics) {
|
|
1034
|
+
if (!isProblemDiagnostic(diagnostic)) continue;
|
|
1035
|
+
if (diagnostic.severity === "error") errors += 1;
|
|
1036
|
+
else if (diagnostic.severity === "warning") warnings += 1;
|
|
1037
|
+
else infos += 1;
|
|
1038
|
+
}
|
|
1039
|
+
return {
|
|
1040
|
+
errors,
|
|
1041
|
+
warnings,
|
|
1042
|
+
infos
|
|
1043
|
+
};
|
|
1044
|
+
}
|
|
1045
|
+
/**
|
|
1046
|
+
* Drops duplicate `problem` diagnostics that share a code, location pointer, and
|
|
1047
|
+
* plugin, so the same issue reported across several passes is shown once. Non-problem
|
|
1048
|
+
* diagnostics are always kept.
|
|
1049
|
+
*/
|
|
1050
|
+
static dedupe(diagnostics) {
|
|
1051
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1052
|
+
const result = [];
|
|
1053
|
+
for (const diagnostic of diagnostics) {
|
|
1054
|
+
if (!isProblemDiagnostic(diagnostic)) {
|
|
1055
|
+
result.push(diagnostic);
|
|
1056
|
+
continue;
|
|
1057
|
+
}
|
|
1058
|
+
const pointer = diagnostic.location && "pointer" in diagnostic.location ? diagnostic.location.pointer : "";
|
|
1059
|
+
const key = `${diagnostic.code} ${pointer} ${diagnostic.plugin ?? ""}`;
|
|
1060
|
+
if (seen.has(key)) continue;
|
|
1061
|
+
seen.add(key);
|
|
1062
|
+
result.push(diagnostic);
|
|
1063
|
+
}
|
|
1064
|
+
return result;
|
|
1065
|
+
}
|
|
1066
|
+
/**
|
|
1067
|
+
* Builds the kubb.dev docs URL for a diagnostic code, e.g.
|
|
1068
|
+
* `KUBB_REF_NOT_FOUND` → `https://kubb.dev/docs/5.x/reference/diagnostics/kubb-ref-not-found`.
|
|
1069
|
+
*/
|
|
1070
|
+
static docsUrl(code) {
|
|
1071
|
+
return `https://kubb.dev/docs/${docsMajor}.x/reference/diagnostics/${code.toLowerCase().replaceAll("_", "-")}`;
|
|
1072
|
+
}
|
|
1073
|
+
/**
|
|
1074
|
+
* The catalog entry for a code: its title, cause, and fix. Mirrors the kubb.dev
|
|
1075
|
+
* `/diagnostics/<slug>` page.
|
|
1076
|
+
*/
|
|
1077
|
+
static explain(code) {
|
|
1078
|
+
return diagnosticCatalog[code];
|
|
1079
|
+
}
|
|
1080
|
+
/**
|
|
1081
|
+
* Reduces a diagnostic to its JSON-safe fields plus a `docsUrl`, for machine-readable
|
|
1082
|
+
* consumers. The `cause`, `kind`, and `duration` are dropped, and absent optional
|
|
1083
|
+
* fields are omitted rather than set to `undefined`.
|
|
1084
|
+
*/
|
|
1085
|
+
static serialize(diagnostic) {
|
|
1086
|
+
const problem = isProblemDiagnostic(diagnostic) ? diagnostic : void 0;
|
|
1087
|
+
return {
|
|
1088
|
+
code: diagnostic.code,
|
|
1089
|
+
severity: diagnostic.severity,
|
|
1090
|
+
message: diagnostic.message,
|
|
1091
|
+
...problem?.location ? { location: problem.location } : {},
|
|
1092
|
+
...problem?.help ? { help: problem.help } : {},
|
|
1093
|
+
...problem?.plugin ? { plugin: problem.plugin } : {},
|
|
1094
|
+
...diagnostic.code === diagnosticCode.unknown ? {} : { docsUrl: Diagnostics.docsUrl(diagnostic.code) }
|
|
1095
|
+
};
|
|
1096
|
+
}
|
|
632
1097
|
};
|
|
633
1098
|
//#endregion
|
|
634
1099
|
//#region src/definePlugin.ts
|
|
@@ -738,7 +1203,7 @@ function defaultResolver(name, type) {
|
|
|
738
1203
|
return camelCase(name);
|
|
739
1204
|
}
|
|
740
1205
|
/**
|
|
741
|
-
* Default option resolver
|
|
1206
|
+
* Default option resolver. Applies include/exclude filters and merges matching override options.
|
|
742
1207
|
*
|
|
743
1208
|
* Returns `null` when the node is filtered out by an `exclude` rule or not matched by any `include` rule.
|
|
744
1209
|
*
|
|
@@ -774,7 +1239,7 @@ function computeOptions(node, options, exclude, include, override) {
|
|
|
774
1239
|
if (isSchemaNode(node)) {
|
|
775
1240
|
if (exclude.some(({ type, pattern }) => matchesSchemaPattern(node, type, pattern) === true)) return null;
|
|
776
1241
|
if (include) {
|
|
777
|
-
const applicable = include.map(({ type, pattern }) => matchesSchemaPattern(node, type, pattern)).filter((
|
|
1242
|
+
const applicable = include.map(({ type, pattern }) => matchesSchemaPattern(node, type, pattern)).filter((result) => result !== null);
|
|
778
1243
|
if (applicable.length > 0 && !applicable.includes(true)) return null;
|
|
779
1244
|
}
|
|
780
1245
|
const overrideOptions = override.find(({ type, pattern }) => matchesSchemaPattern(node, type, pattern) === true)?.options;
|
|
@@ -847,8 +1312,8 @@ function defaultResolvePath({ baseName, pathMode, tag, path: groupPath }, { root
|
|
|
847
1312
|
const result = (() => {
|
|
848
1313
|
if (group && (groupPath || tag)) {
|
|
849
1314
|
const groupValue = group.type === "path" ? groupPath : tag;
|
|
850
|
-
const defaultName = group.type === "tag" ? ({ group:
|
|
851
|
-
const segment =
|
|
1315
|
+
const defaultName = group.type === "tag" ? ({ group: groupName }) => `${camelCase(groupName)}Controller` : ({ group: groupName }) => {
|
|
1316
|
+
const segment = groupName.split("/").filter((part) => part !== "" && part !== "." && part !== "..")[0];
|
|
852
1317
|
return segment ? camelCase(segment) : "";
|
|
853
1318
|
};
|
|
854
1319
|
const resolveName = group.name ?? defaultName;
|
|
@@ -858,7 +1323,13 @@ function defaultResolvePath({ baseName, pathMode, tag, path: groupPath }, { root
|
|
|
858
1323
|
})();
|
|
859
1324
|
const outputDir = path.resolve(root, output.path);
|
|
860
1325
|
const outputDirWithSep = outputDir.endsWith(path.sep) ? outputDir : `${outputDir}${path.sep}`;
|
|
861
|
-
if (result !== outputDir && !result.startsWith(outputDirWithSep)) throw new
|
|
1326
|
+
if (result !== outputDir && !result.startsWith(outputDirWithSep)) throw new DiagnosticError({
|
|
1327
|
+
code: diagnosticCode.pathTraversal,
|
|
1328
|
+
severity: "error",
|
|
1329
|
+
message: `Resolved path "${result}" is outside the output directory "${outputDir}".`,
|
|
1330
|
+
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.",
|
|
1331
|
+
location: { kind: "config" }
|
|
1332
|
+
});
|
|
862
1333
|
return result;
|
|
863
1334
|
}
|
|
864
1335
|
/**
|
|
@@ -866,7 +1337,7 @@ function defaultResolvePath({ baseName, pathMode, tag, path: groupPath }, { root
|
|
|
866
1337
|
*
|
|
867
1338
|
* Resolves a `FileNode` by combining name resolution (`resolver.default`) with
|
|
868
1339
|
* path resolution (`resolver.resolvePath`). The resolved file always has empty
|
|
869
|
-
* `sources`, `imports`, and `exports` arrays
|
|
1340
|
+
* `sources`, `imports`, and `exports` arrays, which consumers populate separately.
|
|
870
1341
|
*
|
|
871
1342
|
* In `single` mode the name is omitted and the file sits directly in the output directory.
|
|
872
1343
|
*
|
|
@@ -942,7 +1413,7 @@ function buildDefaultBanner({ title, description, version, config }) {
|
|
|
942
1413
|
}
|
|
943
1414
|
}
|
|
944
1415
|
/**
|
|
945
|
-
* Default banner resolver
|
|
1416
|
+
* Default banner resolver. Returns the banner string for a generated file.
|
|
946
1417
|
*
|
|
947
1418
|
* A user-supplied `output.banner` overrides the default Kubb "Generated by Kubb" notice.
|
|
948
1419
|
* When no `output.banner` is set, the Kubb notice is used (including `title` and `version`
|
|
@@ -971,7 +1442,7 @@ function buildDefaultBanner({ title, description, version, config }) {
|
|
|
971
1442
|
* // → ''
|
|
972
1443
|
* ```
|
|
973
1444
|
*
|
|
974
|
-
* @example No user banner
|
|
1445
|
+
* @example No user banner, Kubb notice with OAS metadata
|
|
975
1446
|
* ```ts
|
|
976
1447
|
* defaultResolveBanner(meta, { config })
|
|
977
1448
|
* // → '/** Generated by Kubb ... Title: Pet Store ... *\/'
|
|
@@ -997,7 +1468,7 @@ function defaultResolveBanner(meta, { output, config, file }) {
|
|
|
997
1468
|
});
|
|
998
1469
|
}
|
|
999
1470
|
/**
|
|
1000
|
-
* Default footer resolver
|
|
1471
|
+
* Default footer resolver. Returns the footer string for a generated file.
|
|
1001
1472
|
*
|
|
1002
1473
|
* - When `output.footer` is a function, calls it with the file's `BannerMeta` and returns the result.
|
|
1003
1474
|
* - When `output.footer` is a string, returns it directly.
|
|
@@ -1029,11 +1500,11 @@ function defaultResolveFooter(meta, { output, file }) {
|
|
|
1029
1500
|
* name casing, include/exclude/override filtering, output path computation,
|
|
1030
1501
|
* and file construction. Supply your own to override any of them:
|
|
1031
1502
|
*
|
|
1032
|
-
* - `default`
|
|
1033
|
-
* - `resolveOptions`
|
|
1034
|
-
* - `resolvePath`
|
|
1035
|
-
* - `resolveFile`
|
|
1036
|
-
* - `resolveBanner`
|
|
1503
|
+
* - `default` sets the name casing strategy (camelCase or PascalCase).
|
|
1504
|
+
* - `resolveOptions` does include/exclude/override filtering.
|
|
1505
|
+
* - `resolvePath` computes the output path.
|
|
1506
|
+
* - `resolveFile` builds the full `FileNode`.
|
|
1507
|
+
* - `resolveBanner` and `resolveFooter` produce the top and bottom of file text.
|
|
1037
1508
|
*
|
|
1038
1509
|
* Methods in the returned object can call sibling resolver methods via `this`,
|
|
1039
1510
|
* which keeps custom rules small (`this.default(name, 'type')` to delegate).
|
|
@@ -1082,7 +1553,7 @@ function defineResolver(build) {
|
|
|
1082
1553
|
* Encodes an `InputNode` as a compressed, URL-safe string.
|
|
1083
1554
|
*
|
|
1084
1555
|
* The JSON representation is deflate-compressed with {@link deflateSync} before
|
|
1085
|
-
* base64url encoding, which typically reduces payload size by 70
|
|
1556
|
+
* base64url encoding, which typically reduces payload size by 70, 80 % and
|
|
1086
1557
|
* keeps URLs well within browser and server path-length limits.
|
|
1087
1558
|
*/
|
|
1088
1559
|
function encodeAst(input) {
|
|
@@ -1099,8 +1570,7 @@ function getStudioUrl(input, studioUrl, options = {}) {
|
|
|
1099
1570
|
return `${studioUrl.replace(/\/$/, "")}${options.ast ? "/ast" : ""}?root=${encodeAst(input)}`;
|
|
1100
1571
|
}
|
|
1101
1572
|
/**
|
|
1102
|
-
* Opens the Kubb Studio URL for the given `InputNode` in the default browser
|
|
1103
|
-
*
|
|
1573
|
+
* Opens the Kubb Studio URL for the given `InputNode` in the default browser, *
|
|
1104
1574
|
* Falls back to printing the URL if the browser cannot be launched.
|
|
1105
1575
|
*/
|
|
1106
1576
|
async function openInStudio(input, studioUrl, options = {}) {
|
|
@@ -1155,18 +1625,13 @@ function compareFiles(a, b) {
|
|
|
1155
1625
|
* ```
|
|
1156
1626
|
*/
|
|
1157
1627
|
var FileManager = class {
|
|
1158
|
-
#cache = /* @__PURE__ */ new Map();
|
|
1159
|
-
#sorted = null;
|
|
1160
|
-
#onUpsert = null;
|
|
1161
1628
|
/**
|
|
1162
|
-
*
|
|
1163
|
-
* `add`
|
|
1164
|
-
* without keeping its own scan-based diff. Single subscriber by design —
|
|
1165
|
-
* setting again replaces the previous callback. Pass `null` to detach.
|
|
1629
|
+
* Subscribe to file-store changes. Listeners on `upsert` see each resolved file as it lands
|
|
1630
|
+
* through `add` or `upsert`.
|
|
1166
1631
|
*/
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1632
|
+
hooks = new AsyncEventEmitter();
|
|
1633
|
+
#cache = /* @__PURE__ */ new Map();
|
|
1634
|
+
#sorted = null;
|
|
1170
1635
|
add(...files) {
|
|
1171
1636
|
return this.#store(files, false);
|
|
1172
1637
|
}
|
|
@@ -1181,7 +1646,7 @@ var FileManager = class {
|
|
|
1181
1646
|
const merged = existing && mergeExisting ? createFile(mergeFile(existing, file)) : createFile(file);
|
|
1182
1647
|
this.#cache.set(merged.path, merged);
|
|
1183
1648
|
resolved.push(merged);
|
|
1184
|
-
this
|
|
1649
|
+
this.hooks.emit("upsert", merged);
|
|
1185
1650
|
}
|
|
1186
1651
|
if (resolved.length > 0) this.#sorted = null;
|
|
1187
1652
|
return resolved;
|
|
@@ -1206,18 +1671,19 @@ var FileManager = class {
|
|
|
1206
1671
|
this.#sorted = null;
|
|
1207
1672
|
}
|
|
1208
1673
|
/**
|
|
1209
|
-
* Releases all stored files. Called by the core after
|
|
1674
|
+
* Releases all stored files and clears every `hooks` listener. Called by the core after
|
|
1675
|
+
* `kubb:build:end`.
|
|
1210
1676
|
*/
|
|
1211
1677
|
dispose() {
|
|
1212
1678
|
this.clear();
|
|
1213
|
-
this
|
|
1679
|
+
this.hooks.removeAll();
|
|
1214
1680
|
}
|
|
1215
1681
|
[Symbol.dispose]() {
|
|
1216
1682
|
this.dispose();
|
|
1217
1683
|
}
|
|
1218
1684
|
/**
|
|
1219
1685
|
* All stored files in stable sort order (shortest path first, barrel files
|
|
1220
|
-
* last within a length bucket). Returns a cached view
|
|
1686
|
+
* last within a length bucket). Returns a cached view, do not mutate.
|
|
1221
1687
|
*/
|
|
1222
1688
|
get files() {
|
|
1223
1689
|
return this.#sorted ??= [...this.#cache.values()].sort(compareFiles);
|
|
@@ -1230,32 +1696,57 @@ function joinSources(file) {
|
|
|
1230
1696
|
if (sources.length === 0) return "";
|
|
1231
1697
|
const parts = [];
|
|
1232
1698
|
for (const source of sources) {
|
|
1233
|
-
const
|
|
1234
|
-
if (
|
|
1699
|
+
const text = extractStringsFromNodes(source.nodes);
|
|
1700
|
+
if (text) parts.push(text);
|
|
1235
1701
|
}
|
|
1236
1702
|
return parts.join("\n\n");
|
|
1237
1703
|
}
|
|
1238
1704
|
/**
|
|
1239
|
-
*
|
|
1240
|
-
*
|
|
1705
|
+
* Turns `FileNode`s into source strings and writes them to storage.
|
|
1706
|
+
*
|
|
1707
|
+
* Two modes share the same instance. Stateless mode (`parse`, `stream`, `run`) just runs the
|
|
1708
|
+
* conversion. Queue mode (`enqueue`, `flush`, `drain`) buffers files deduped by path and
|
|
1709
|
+
* writes each batch through storage with up to `STREAM_FLUSH_EVERY` requests in flight.
|
|
1710
|
+
*
|
|
1711
|
+
* `flush` does not wait for its batch to finish, so dispatch can overlap with IO. The next
|
|
1712
|
+
* `flush` or `drain` picks the in-flight batch up. `drain` blocks until everything has been
|
|
1713
|
+
* written and is meant for the end of a build.
|
|
1241
1714
|
*
|
|
1242
|
-
*
|
|
1715
|
+
* To surface build-level hook signals (`kubb:files:processing:*` and friends) subscribe to
|
|
1716
|
+
* `hooks` and re-emit on the kubb bus.
|
|
1243
1717
|
*/
|
|
1244
1718
|
var FileProcessor = class {
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1719
|
+
hooks = new AsyncEventEmitter();
|
|
1720
|
+
#parsers;
|
|
1721
|
+
#storage;
|
|
1722
|
+
#extension;
|
|
1723
|
+
#pending = /* @__PURE__ */ new Map();
|
|
1724
|
+
#runningFlush = null;
|
|
1725
|
+
constructor(options) {
|
|
1726
|
+
this.#parsers = options.parsers ?? null;
|
|
1727
|
+
this.#storage = options.storage;
|
|
1728
|
+
this.#extension = options.extension ?? null;
|
|
1729
|
+
}
|
|
1730
|
+
/**
|
|
1731
|
+
* Files waiting in the queue.
|
|
1732
|
+
*/
|
|
1733
|
+
get size() {
|
|
1734
|
+
return this.#pending.size;
|
|
1735
|
+
}
|
|
1736
|
+
parse(file) {
|
|
1737
|
+
const parsers = this.#parsers;
|
|
1738
|
+
const parseExtName = this.#extension?.[file.extname] || void 0;
|
|
1248
1739
|
if (!parsers || !file.extname) return joinSources(file);
|
|
1249
1740
|
const parser = parsers.get(file.extname);
|
|
1250
1741
|
if (!parser) return joinSources(file);
|
|
1251
1742
|
return parser.parse(file, { extname: parseExtName });
|
|
1252
1743
|
}
|
|
1253
|
-
*stream(files
|
|
1744
|
+
*stream(files) {
|
|
1254
1745
|
const total = files.length;
|
|
1255
1746
|
if (total === 0) return;
|
|
1256
1747
|
let processed = 0;
|
|
1257
1748
|
for (const file of files) {
|
|
1258
|
-
const source = this.parse(file
|
|
1749
|
+
const source = this.parse(file);
|
|
1259
1750
|
processed++;
|
|
1260
1751
|
yield {
|
|
1261
1752
|
file,
|
|
@@ -1266,30 +1757,158 @@ var FileProcessor = class {
|
|
|
1266
1757
|
};
|
|
1267
1758
|
}
|
|
1268
1759
|
}
|
|
1269
|
-
async run(files
|
|
1270
|
-
await this.
|
|
1271
|
-
for (const { file, source, processed, total, percentage } of this.stream(files
|
|
1760
|
+
async run(files) {
|
|
1761
|
+
await this.hooks.emit("start", files);
|
|
1762
|
+
for (const { file, source, processed, total, percentage } of this.stream(files)) await this.hooks.emit("update", {
|
|
1272
1763
|
file,
|
|
1273
1764
|
source,
|
|
1274
1765
|
processed,
|
|
1275
1766
|
percentage,
|
|
1276
1767
|
total
|
|
1277
1768
|
});
|
|
1278
|
-
await this.
|
|
1769
|
+
await this.hooks.emit("end", files);
|
|
1279
1770
|
return files;
|
|
1280
1771
|
}
|
|
1281
1772
|
/**
|
|
1282
|
-
*
|
|
1773
|
+
* Adds a file to the next flush. A later `enqueue` for the same path replaces the previous
|
|
1774
|
+
* entry, matching `FileManager.upsert`. Fires the `enqueue` event.
|
|
1775
|
+
*/
|
|
1776
|
+
enqueue(file) {
|
|
1777
|
+
this.#pending.set(file.path, file);
|
|
1778
|
+
this.hooks.emit("enqueue", file);
|
|
1779
|
+
}
|
|
1780
|
+
/**
|
|
1781
|
+
* Starts processing the queued files. Waits for any previous flush to finish (so two
|
|
1782
|
+
* batches never run together) and then returns without waiting for the new one. The next
|
|
1783
|
+
* `flush` or `drain` picks up the in-flight task.
|
|
1784
|
+
*/
|
|
1785
|
+
async flush() {
|
|
1786
|
+
if (this.#runningFlush) await this.#runningFlush;
|
|
1787
|
+
if (this.#pending.size === 0) return;
|
|
1788
|
+
const batch = [...this.#pending.values()];
|
|
1789
|
+
this.#pending.clear();
|
|
1790
|
+
this.#runningFlush = this.#processAndWrite(batch).finally(() => {
|
|
1791
|
+
this.#runningFlush = null;
|
|
1792
|
+
});
|
|
1793
|
+
}
|
|
1794
|
+
/**
|
|
1795
|
+
* Waits for the in-flight flush and writes any files still queued. Fires the `drain` event
|
|
1796
|
+
* when both are done.
|
|
1797
|
+
*/
|
|
1798
|
+
async drain() {
|
|
1799
|
+
if (this.#runningFlush) await this.#runningFlush;
|
|
1800
|
+
if (this.#pending.size > 0) {
|
|
1801
|
+
const batch = [...this.#pending.values()];
|
|
1802
|
+
this.#pending.clear();
|
|
1803
|
+
await this.#processAndWrite(batch);
|
|
1804
|
+
}
|
|
1805
|
+
await this.hooks.emit("drain");
|
|
1806
|
+
}
|
|
1807
|
+
async #processAndWrite(files) {
|
|
1808
|
+
const storage = this.#storage;
|
|
1809
|
+
await this.hooks.emit("start", files);
|
|
1810
|
+
const items = [...this.stream(files)];
|
|
1811
|
+
for (const item of items) await this.hooks.emit("update", item);
|
|
1812
|
+
const queue = [];
|
|
1813
|
+
for (const { file, source } of items) if (source) {
|
|
1814
|
+
queue.push(storage.setItem(file.path, source));
|
|
1815
|
+
if (queue.length >= 50) await Promise.all(queue.splice(0));
|
|
1816
|
+
}
|
|
1817
|
+
await Promise.all(queue);
|
|
1818
|
+
await this.hooks.emit("end", files);
|
|
1819
|
+
}
|
|
1820
|
+
/**
|
|
1821
|
+
* Clears every listener and the pending queue.
|
|
1283
1822
|
*/
|
|
1284
1823
|
dispose() {
|
|
1285
|
-
this.
|
|
1824
|
+
this.hooks.removeAll();
|
|
1825
|
+
this.#pending.clear();
|
|
1286
1826
|
}
|
|
1287
1827
|
[Symbol.dispose]() {
|
|
1288
1828
|
this.dispose();
|
|
1289
1829
|
}
|
|
1290
1830
|
};
|
|
1291
1831
|
//#endregion
|
|
1292
|
-
//#region
|
|
1832
|
+
//#region src/HookRegistry.ts
|
|
1833
|
+
/**
|
|
1834
|
+
* Listener bookkeeping around an `AsyncEventEmitter`. Listeners attached through `register`
|
|
1835
|
+
* stay on the emitter but are tracked so `dispose()` removes only them, listeners attached
|
|
1836
|
+
* directly via `emitter.on(...)` survive.
|
|
1837
|
+
*/
|
|
1838
|
+
var HookRegistry = class {
|
|
1839
|
+
#emitter;
|
|
1840
|
+
#entries = /* @__PURE__ */ new Set();
|
|
1841
|
+
constructor(options) {
|
|
1842
|
+
this.#emitter = options.emitter;
|
|
1843
|
+
}
|
|
1844
|
+
get emitter() {
|
|
1845
|
+
return this.#emitter;
|
|
1846
|
+
}
|
|
1847
|
+
get size() {
|
|
1848
|
+
return this.#entries.size;
|
|
1849
|
+
}
|
|
1850
|
+
register(options) {
|
|
1851
|
+
this.#emitter.on(options.event, options.handler);
|
|
1852
|
+
this.#entries.add(options);
|
|
1853
|
+
}
|
|
1854
|
+
dispose() {
|
|
1855
|
+
for (const entry of this.#entries) this.#emitter.off(entry.event, entry.handler);
|
|
1856
|
+
this.#entries.clear();
|
|
1857
|
+
}
|
|
1858
|
+
};
|
|
1859
|
+
//#endregion
|
|
1860
|
+
//#region src/Transform.ts
|
|
1861
|
+
/**
|
|
1862
|
+
* Holds one `Visitor` per plugin, keyed by plugin name. Each plugin's transformer runs in
|
|
1863
|
+
* isolation on the original adapter node. `applyTo` is a lookup, not a chain, so plugin A's
|
|
1864
|
+
* visitor never sees plugin B's output. When no transformer is registered, `applyTo` returns
|
|
1865
|
+
* the original node reference, and the `@kubb/ast` `transform` primitive does the same when
|
|
1866
|
+
* its visitor leaves the tree untouched. Callers can compare by identity to detect a no-op.
|
|
1867
|
+
*
|
|
1868
|
+
* Registration order matches the order setup hooks fire, which the driver has already sorted
|
|
1869
|
+
* by `enforce` and dependency edges. The registry does not re-order anything.
|
|
1870
|
+
*/
|
|
1871
|
+
var Transform = class {
|
|
1872
|
+
#visitors = /* @__PURE__ */ new Map();
|
|
1873
|
+
/**
|
|
1874
|
+
* Number of plugins with a registered transformer.
|
|
1875
|
+
*/
|
|
1876
|
+
get size() {
|
|
1877
|
+
return this.#visitors.size;
|
|
1878
|
+
}
|
|
1879
|
+
/**
|
|
1880
|
+
* Records `visitor` as the transformer for `pluginName`. A second call for the same plugin
|
|
1881
|
+
* replaces the first.
|
|
1882
|
+
*/
|
|
1883
|
+
register(pluginName, visitor) {
|
|
1884
|
+
this.#visitors.set(pluginName, visitor);
|
|
1885
|
+
}
|
|
1886
|
+
/**
|
|
1887
|
+
* Looks up the transformer for `pluginName`. The generator context uses this so plugins can
|
|
1888
|
+
* read their own visitor through `ctx.transformer`.
|
|
1889
|
+
*/
|
|
1890
|
+
get(pluginName) {
|
|
1891
|
+
return this.#visitors.get(pluginName);
|
|
1892
|
+
}
|
|
1893
|
+
/**
|
|
1894
|
+
* Runs the plugin's transformer on `node`. Returns the original node reference when the
|
|
1895
|
+
* plugin has no transformer, so callers can compare by identity to detect a no-op.
|
|
1896
|
+
*/
|
|
1897
|
+
applyTo(pluginName, node) {
|
|
1898
|
+
const visitor = this.#visitors.get(pluginName);
|
|
1899
|
+
if (!visitor) return node;
|
|
1900
|
+
return transform(node, visitor);
|
|
1901
|
+
}
|
|
1902
|
+
/**
|
|
1903
|
+
* Clears every registration. Called from the driver's `dispose()` so visitors do not leak
|
|
1904
|
+
* across builds.
|
|
1905
|
+
*/
|
|
1906
|
+
dispose() {
|
|
1907
|
+
this.#visitors.clear();
|
|
1908
|
+
}
|
|
1909
|
+
};
|
|
1910
|
+
//#endregion
|
|
1911
|
+
//#region \0@oxc-project+runtime@0.133.0/helpers/esm/usingCtx.js
|
|
1293
1912
|
function _usingCtx() {
|
|
1294
1913
|
var r = "function" == typeof SuppressedError ? SuppressedError : function(r, e) {
|
|
1295
1914
|
var n = Error();
|
|
@@ -1353,13 +1972,6 @@ function _usingCtx() {
|
|
|
1353
1972
|
function enforceOrder(enforce) {
|
|
1354
1973
|
return enforce === "pre" ? -1 : enforce === "post" ? 1 : 0;
|
|
1355
1974
|
}
|
|
1356
|
-
const OPERATION_FILTER_TYPES = new Set([
|
|
1357
|
-
"tag",
|
|
1358
|
-
"operationId",
|
|
1359
|
-
"path",
|
|
1360
|
-
"method",
|
|
1361
|
-
"contentType"
|
|
1362
|
-
]);
|
|
1363
1975
|
var KubbDriver = class KubbDriver {
|
|
1364
1976
|
config;
|
|
1365
1977
|
options;
|
|
@@ -1377,7 +1989,7 @@ var KubbDriver = class KubbDriver {
|
|
|
1377
1989
|
}
|
|
1378
1990
|
/**
|
|
1379
1991
|
* The streaming `InputStreamNode` produced by the adapter.
|
|
1380
|
-
* Always set after adapter setup
|
|
1992
|
+
* Always set after adapter setup, parse-only adapters are wrapped automatically.
|
|
1381
1993
|
*/
|
|
1382
1994
|
inputNode = null;
|
|
1383
1995
|
adapter = null;
|
|
@@ -1385,7 +1997,7 @@ var KubbDriver = class KubbDriver {
|
|
|
1385
1997
|
* Studio session state, kept together so `dispose()` can reset it atomically.
|
|
1386
1998
|
*
|
|
1387
1999
|
* - `source` holds the raw adapter source so `adapter.parse()` can be called lazily.
|
|
1388
|
-
* Intentionally outlives the build
|
|
2000
|
+
* Intentionally outlives the build, cleared by `dispose()`.
|
|
1389
2001
|
* - `isOpen` prevents opening the studio more than once per build.
|
|
1390
2002
|
* - `inputNode` caches the parse promise so `adapter.parse()` is called at most once
|
|
1391
2003
|
* per studio session, even when `openInStudio()` is called multiple times.
|
|
@@ -1395,14 +2007,12 @@ var KubbDriver = class KubbDriver {
|
|
|
1395
2007
|
isOpen: false,
|
|
1396
2008
|
inputNode: null
|
|
1397
2009
|
};
|
|
1398
|
-
#middlewareListeners = [];
|
|
1399
2010
|
/**
|
|
1400
2011
|
* Central file store for all generated files.
|
|
1401
2012
|
* Plugins should use `this.addFile()` / `this.upsertFile()` (via their context) to
|
|
1402
|
-
* add files
|
|
2013
|
+
* add files. This property gives direct read/write access when needed.
|
|
1403
2014
|
*/
|
|
1404
2015
|
fileManager = new FileManager();
|
|
1405
|
-
#fileProcessor = new FileProcessor();
|
|
1406
2016
|
plugins = /* @__PURE__ */ new Map();
|
|
1407
2017
|
/**
|
|
1408
2018
|
* Tracks which plugins have generators registered via `addGenerator()` (event-based path).
|
|
@@ -1411,11 +2021,22 @@ var KubbDriver = class KubbDriver {
|
|
|
1411
2021
|
#eventGeneratorPlugins = /* @__PURE__ */ new Set();
|
|
1412
2022
|
#resolvers = /* @__PURE__ */ new Map();
|
|
1413
2023
|
#defaultResolvers = /* @__PURE__ */ new Map();
|
|
1414
|
-
|
|
2024
|
+
/**
|
|
2025
|
+
* Tracks every listener the driver added (plugin, middleware, generator) so `dispose()` can
|
|
2026
|
+
* remove them in one pass. Middleware registers after plugins, so it fires last via `Set`
|
|
2027
|
+
* insertion order. External `hooks.on(...)` listeners are not tracked.
|
|
2028
|
+
*/
|
|
2029
|
+
#registry;
|
|
2030
|
+
/**
|
|
2031
|
+
* Transform registry. Plugins populate it during `kubb:plugin:setup` via `setTransformer`,
|
|
2032
|
+
* and `#runGenerators` reads it once per `(plugin, node)` pair through `applyTo`.
|
|
2033
|
+
*/
|
|
2034
|
+
#transforms = new Transform();
|
|
1415
2035
|
constructor(config, options) {
|
|
1416
2036
|
this.config = config;
|
|
1417
2037
|
this.options = options;
|
|
1418
2038
|
this.adapter = config.adapter ?? null;
|
|
2039
|
+
this.#registry = new HookRegistry({ emitter: options.hooks });
|
|
1419
2040
|
}
|
|
1420
2041
|
async setup() {
|
|
1421
2042
|
const normalized = this.config.plugins.map((rawPlugin) => this.#normalizePlugin(rawPlugin));
|
|
@@ -1430,7 +2051,7 @@ var KubbDriver = class KubbDriver {
|
|
|
1430
2051
|
this.plugins.set(plugin.name, plugin);
|
|
1431
2052
|
}
|
|
1432
2053
|
if (this.config.middleware) for (const middleware of this.config.middleware) for (const event of Object.keys(middleware.hooks)) this.#registerMiddleware(event, middleware.hooks);
|
|
1433
|
-
if (this.config.adapter)
|
|
2054
|
+
if (this.config.adapter) this.#studio.source = inputToAdapterSource(this.config);
|
|
1434
2055
|
}
|
|
1435
2056
|
get hooks() {
|
|
1436
2057
|
return this.options.hooks;
|
|
@@ -1454,46 +2075,39 @@ var KubbDriver = class KubbDriver {
|
|
|
1454
2075
|
if ("apply" in plugin && typeof plugin.apply === "function") normalized.apply = plugin.apply;
|
|
1455
2076
|
return normalized;
|
|
1456
2077
|
}
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
2078
|
+
/**
|
|
2079
|
+
* Parses the adapter source into `this.inputNode`. Idempotent, so repeated calls from
|
|
2080
|
+
* `run` or the studio path do not re-parse. Adapters with `stream()` are used directly.
|
|
2081
|
+
* Adapters with only `parse()` are wrapped via `createStreamInput` so the dispatch loop
|
|
2082
|
+
* stays stream-only.
|
|
2083
|
+
*/
|
|
2084
|
+
async #parseInput() {
|
|
2085
|
+
if (this.inputNode || !this.adapter || !this.#studio.source) return;
|
|
2086
|
+
const adapter = this.adapter;
|
|
2087
|
+
const source = this.#studio.source;
|
|
1460
2088
|
if (adapter.stream) {
|
|
1461
2089
|
this.inputNode = await adapter.stream(source);
|
|
1462
|
-
|
|
1463
|
-
date: /* @__PURE__ */ new Date(),
|
|
1464
|
-
logs: [`✓ Adapter '${adapter.name}' producing input stream`]
|
|
1465
|
-
});
|
|
1466
|
-
} else {
|
|
1467
|
-
const inputNode = await adapter.parse(source);
|
|
1468
|
-
this.inputNode = createStreamInput(arrayToAsyncIterable(inputNode.schemas), arrayToAsyncIterable(inputNode.operations), inputNode.meta);
|
|
1469
|
-
await this.hooks.emit("kubb:debug", {
|
|
1470
|
-
date: /* @__PURE__ */ new Date(),
|
|
1471
|
-
logs: [
|
|
1472
|
-
`✓ Adapter '${adapter.name}' resolved InputNode (wrapped as stream)`,
|
|
1473
|
-
` • Schemas: ${inputNode.schemas.length}`,
|
|
1474
|
-
` • Operations: ${inputNode.operations.length}`
|
|
1475
|
-
]
|
|
1476
|
-
});
|
|
2090
|
+
return;
|
|
1477
2091
|
}
|
|
2092
|
+
const parsed = await adapter.parse(source);
|
|
2093
|
+
this.inputNode = createStreamInput(arrayToAsyncIterable(parsed.schemas), arrayToAsyncIterable(parsed.operations), parsed.meta);
|
|
1478
2094
|
}
|
|
1479
2095
|
#registerMiddleware(event, middlewareHooks) {
|
|
1480
2096
|
const handler = middlewareHooks[event];
|
|
1481
2097
|
if (!handler) return;
|
|
1482
|
-
this.
|
|
1483
|
-
|
|
2098
|
+
this.#registry.register({
|
|
2099
|
+
event,
|
|
2100
|
+
handler,
|
|
2101
|
+
source: "middleware"
|
|
2102
|
+
});
|
|
1484
2103
|
}
|
|
1485
2104
|
/**
|
|
1486
2105
|
* Registers a hook-style plugin's lifecycle handlers on the shared `AsyncEventEmitter`.
|
|
1487
2106
|
*
|
|
1488
|
-
*
|
|
1489
|
-
*
|
|
1490
|
-
* `
|
|
1491
|
-
*
|
|
1492
|
-
* All other hooks are iterated and registered directly as pass-through listeners.
|
|
1493
|
-
* Any event key present in the global `KubbHooks` interface can be subscribed to.
|
|
1494
|
-
*
|
|
1495
|
-
* External tooling can subscribe to any of these events via `hooks.on(...)` to observe
|
|
1496
|
-
* the plugin lifecycle without modifying plugin behavior.
|
|
2107
|
+
* The `kubb:plugin:setup` listener wraps the global context in a plugin-specific one so
|
|
2108
|
+
* `addGenerator`, `setResolver`, and `setTransformer` target the right `normalizedPlugin`.
|
|
2109
|
+
* Every other `KubbHooks` event registers as a pass-through listener that external tooling
|
|
2110
|
+
* can observe via `hooks.on(...)`.
|
|
1497
2111
|
*
|
|
1498
2112
|
* @internal
|
|
1499
2113
|
*/
|
|
@@ -1512,10 +2126,7 @@ var KubbDriver = class KubbDriver {
|
|
|
1512
2126
|
this.setPluginResolver(plugin.name, resolver);
|
|
1513
2127
|
},
|
|
1514
2128
|
setTransformer: (visitor) => {
|
|
1515
|
-
plugin.
|
|
1516
|
-
},
|
|
1517
|
-
setRenderer: (renderer) => {
|
|
1518
|
-
plugin.renderer = renderer;
|
|
2129
|
+
this.#transforms.register(plugin.name, visitor);
|
|
1519
2130
|
},
|
|
1520
2131
|
setOptions: (opts) => {
|
|
1521
2132
|
plugin.options = {
|
|
@@ -1529,13 +2140,21 @@ var KubbDriver = class KubbDriver {
|
|
|
1529
2140
|
};
|
|
1530
2141
|
return hooks["kubb:plugin:setup"](pluginCtx);
|
|
1531
2142
|
};
|
|
1532
|
-
this.
|
|
1533
|
-
|
|
2143
|
+
this.#registry.register({
|
|
2144
|
+
event: "kubb:plugin:setup",
|
|
2145
|
+
handler: setupHandler,
|
|
2146
|
+
source: "plugin"
|
|
2147
|
+
});
|
|
1534
2148
|
}
|
|
1535
|
-
for (const
|
|
1536
|
-
if (event === "kubb:plugin:setup"
|
|
1537
|
-
|
|
1538
|
-
|
|
2149
|
+
for (const event of Object.keys(hooks)) {
|
|
2150
|
+
if (event === "kubb:plugin:setup") continue;
|
|
2151
|
+
const handler = hooks[event];
|
|
2152
|
+
if (!handler) continue;
|
|
2153
|
+
this.#registry.register({
|
|
2154
|
+
event,
|
|
2155
|
+
handler,
|
|
2156
|
+
source: "plugin"
|
|
2157
|
+
});
|
|
1539
2158
|
}
|
|
1540
2159
|
}
|
|
1541
2160
|
/**
|
|
@@ -1552,7 +2171,6 @@ var KubbDriver = class KubbDriver {
|
|
|
1552
2171
|
addGenerator: noop,
|
|
1553
2172
|
setResolver: noop,
|
|
1554
2173
|
setTransformer: noop,
|
|
1555
|
-
setRenderer: noop,
|
|
1556
2174
|
setOptions: noop,
|
|
1557
2175
|
injectFile: noop,
|
|
1558
2176
|
updateConfig: noop
|
|
@@ -1566,52 +2184,56 @@ var KubbDriver = class KubbDriver {
|
|
|
1566
2184
|
* respectively. Each listener is scoped to the owning plugin via a `ctx.plugin.name` check
|
|
1567
2185
|
* so that generators from different plugins do not cross-fire.
|
|
1568
2186
|
*
|
|
1569
|
-
* The renderer
|
|
1570
|
-
*
|
|
1571
|
-
* declares a renderer.
|
|
2187
|
+
* The renderer comes from `generator.renderer`. Set `generator.renderer = null` (or leave it
|
|
2188
|
+
* unset) to opt out of rendering.
|
|
1572
2189
|
*
|
|
1573
2190
|
* Call this method inside `addGenerator()` (in `kubb:plugin:setup`) to wire up a generator.
|
|
1574
2191
|
*/
|
|
1575
|
-
registerGenerator(pluginName,
|
|
1576
|
-
|
|
1577
|
-
const plugin = this.plugins.get(pluginName);
|
|
1578
|
-
return gen.renderer === null ? void 0 : gen.renderer ?? plugin?.renderer ?? this.config.renderer;
|
|
1579
|
-
};
|
|
1580
|
-
if (gen.schema) {
|
|
2192
|
+
registerGenerator(pluginName, generator) {
|
|
2193
|
+
if (generator.schema) {
|
|
1581
2194
|
const schemaHandler = async (node, ctx) => {
|
|
1582
2195
|
if (ctx.plugin.name !== pluginName) return;
|
|
1583
|
-
await
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
2196
|
+
const result = await generator.schema(node, ctx);
|
|
2197
|
+
await this.dispatch({
|
|
2198
|
+
result,
|
|
2199
|
+
renderer: generator.renderer
|
|
1587
2200
|
});
|
|
1588
2201
|
};
|
|
1589
|
-
this.
|
|
1590
|
-
|
|
2202
|
+
this.#registry.register({
|
|
2203
|
+
event: "kubb:generate:schema",
|
|
2204
|
+
handler: schemaHandler,
|
|
2205
|
+
source: "driver"
|
|
2206
|
+
});
|
|
1591
2207
|
}
|
|
1592
|
-
if (
|
|
2208
|
+
if (generator.operation) {
|
|
1593
2209
|
const operationHandler = async (node, ctx) => {
|
|
1594
2210
|
if (ctx.plugin.name !== pluginName) return;
|
|
1595
|
-
await
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
2211
|
+
const result = await generator.operation(node, ctx);
|
|
2212
|
+
await this.dispatch({
|
|
2213
|
+
result,
|
|
2214
|
+
renderer: generator.renderer
|
|
1599
2215
|
});
|
|
1600
2216
|
};
|
|
1601
|
-
this.
|
|
1602
|
-
|
|
2217
|
+
this.#registry.register({
|
|
2218
|
+
event: "kubb:generate:operation",
|
|
2219
|
+
handler: operationHandler,
|
|
2220
|
+
source: "driver"
|
|
2221
|
+
});
|
|
1603
2222
|
}
|
|
1604
|
-
if (
|
|
2223
|
+
if (generator.operations) {
|
|
1605
2224
|
const operationsHandler = async (nodes, ctx) => {
|
|
1606
2225
|
if (ctx.plugin.name !== pluginName) return;
|
|
1607
|
-
await
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
2226
|
+
const result = await generator.operations(nodes, ctx);
|
|
2227
|
+
await this.dispatch({
|
|
2228
|
+
result,
|
|
2229
|
+
renderer: generator.renderer
|
|
1611
2230
|
});
|
|
1612
2231
|
};
|
|
1613
|
-
this.
|
|
1614
|
-
|
|
2232
|
+
this.#registry.register({
|
|
2233
|
+
event: "kubb:generate:operations",
|
|
2234
|
+
handler: operationsHandler,
|
|
2235
|
+
source: "driver"
|
|
2236
|
+
});
|
|
1615
2237
|
}
|
|
1616
2238
|
this.#eventGeneratorPlugins.add(pluginName);
|
|
1617
2239
|
}
|
|
@@ -1626,143 +2248,110 @@ var KubbDriver = class KubbDriver {
|
|
|
1626
2248
|
return this.#eventGeneratorPlugins.has(pluginName);
|
|
1627
2249
|
}
|
|
1628
2250
|
/**
|
|
1629
|
-
* Runs the full plugin pipeline. Returns
|
|
1630
|
-
* when an outer hook throws
|
|
1631
|
-
* the
|
|
2251
|
+
* Runs the full plugin pipeline. Returns the diagnostics collected so far even
|
|
2252
|
+
* when an outer hook throws, since the orchestrator preserves partial state by capturing
|
|
2253
|
+
* the failure as a {@link Diagnostic} instead of propagating. Each plugin also
|
|
2254
|
+
* contributes a `timing` diagnostic for the run summary.
|
|
1632
2255
|
*/
|
|
1633
2256
|
async run({ storage }) {
|
|
1634
|
-
const hooks = this
|
|
1635
|
-
const
|
|
1636
|
-
const failedPlugins = /* @__PURE__ */ new Set();
|
|
1637
|
-
const pluginTimings = /* @__PURE__ */ new Map();
|
|
2257
|
+
const { hooks, config } = this;
|
|
2258
|
+
const diagnostics = [];
|
|
1638
2259
|
const parsersMap = /* @__PURE__ */ new Map();
|
|
1639
2260
|
for (const parser of config.parsers) if (parser.extNames) for (const ext of parser.extNames) parsersMap.set(ext, parser);
|
|
1640
|
-
const
|
|
1641
|
-
|
|
1642
|
-
|
|
2261
|
+
const processor = new FileProcessor({
|
|
2262
|
+
parsers: parsersMap,
|
|
2263
|
+
storage,
|
|
2264
|
+
extension: config.output.extension
|
|
1643
2265
|
});
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
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
|
-
|
|
2266
|
+
processor.hooks.on("start", async (files) => {
|
|
2267
|
+
await hooks.emit("kubb:files:processing:start", { files });
|
|
2268
|
+
});
|
|
2269
|
+
const updateBuffer = [];
|
|
2270
|
+
processor.hooks.on("update", (item) => {
|
|
2271
|
+
updateBuffer.push(item);
|
|
2272
|
+
});
|
|
2273
|
+
processor.hooks.on("end", async (files) => {
|
|
2274
|
+
await hooks.emit("kubb:files:processing:update", { files: updateBuffer.map((item) => ({
|
|
2275
|
+
...item,
|
|
2276
|
+
config
|
|
2277
|
+
})) });
|
|
2278
|
+
updateBuffer.length = 0;
|
|
2279
|
+
await hooks.emit("kubb:files:processing:end", { files });
|
|
2280
|
+
});
|
|
2281
|
+
const onFileUpsert = (file) => {
|
|
2282
|
+
processor.enqueue(file);
|
|
2283
|
+
};
|
|
2284
|
+
this.fileManager.hooks.on("upsert", onFileUpsert);
|
|
2285
|
+
return Diagnostics.scope((diagnostic) => diagnostics.push(diagnostic), async () => {
|
|
2286
|
+
try {
|
|
2287
|
+
await this.#parseInput();
|
|
2288
|
+
await this.emitSetupHooks();
|
|
2289
|
+
if (this.adapter && this.inputNode) await hooks.emit("kubb:build:start", Object.assign({
|
|
2290
|
+
config,
|
|
2291
|
+
adapter: this.adapter,
|
|
2292
|
+
meta: this.inputNode.meta,
|
|
2293
|
+
getPlugin: this.getPlugin.bind(this)
|
|
2294
|
+
}, this.#filesPayload()));
|
|
2295
|
+
const generatorPlugins = [];
|
|
2296
|
+
for (const plugin of this.plugins.values()) {
|
|
2297
|
+
const context = this.getContext(plugin);
|
|
2298
|
+
const hrStart = process.hrtime();
|
|
2299
|
+
try {
|
|
2300
|
+
await hooks.emit("kubb:plugin:start", { plugin });
|
|
2301
|
+
} catch (caughtError) {
|
|
2302
|
+
const error = caughtError;
|
|
2303
|
+
const duration = getElapsedMs(hrStart);
|
|
2304
|
+
await this.#emitPluginEnd({
|
|
2305
|
+
plugin,
|
|
2306
|
+
duration,
|
|
2307
|
+
success: false,
|
|
2308
|
+
error
|
|
2309
|
+
});
|
|
2310
|
+
diagnostics.push({
|
|
2311
|
+
...Diagnostics.from(error),
|
|
2312
|
+
plugin: plugin.name
|
|
2313
|
+
}, Diagnostics.performance({
|
|
2314
|
+
plugin: plugin.name,
|
|
2315
|
+
duration
|
|
2316
|
+
}));
|
|
2317
|
+
continue;
|
|
2318
|
+
}
|
|
2319
|
+
if (this.hasEventGenerators(plugin.name)) {
|
|
2320
|
+
generatorPlugins.push({
|
|
2321
|
+
plugin,
|
|
2322
|
+
context,
|
|
2323
|
+
hrStart
|
|
2324
|
+
});
|
|
2325
|
+
continue;
|
|
2326
|
+
}
|
|
1697
2327
|
const duration = getElapsedMs(hrStart);
|
|
1698
|
-
|
|
2328
|
+
diagnostics.push(Diagnostics.performance({
|
|
2329
|
+
plugin: plugin.name,
|
|
2330
|
+
duration
|
|
2331
|
+
}));
|
|
1699
2332
|
await this.#emitPluginEnd({
|
|
1700
2333
|
plugin,
|
|
1701
2334
|
duration,
|
|
1702
|
-
success:
|
|
1703
|
-
error
|
|
2335
|
+
success: true
|
|
1704
2336
|
});
|
|
1705
|
-
failedPlugins.add({
|
|
1706
|
-
plugin,
|
|
1707
|
-
error
|
|
1708
|
-
});
|
|
1709
|
-
continue;
|
|
1710
|
-
}
|
|
1711
|
-
if (plugin.generators?.length || this.hasEventGenerators(plugin.name)) {
|
|
1712
|
-
generatorPlugins.push({
|
|
1713
|
-
plugin,
|
|
1714
|
-
context,
|
|
1715
|
-
hrStart
|
|
1716
|
-
});
|
|
1717
|
-
continue;
|
|
1718
2337
|
}
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
await this.#
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
date: /* @__PURE__ */ new Date(),
|
|
1728
|
-
logs: [`✓ Plugin started successfully (${formatMs(duration)})`]
|
|
1729
|
-
});
|
|
1730
|
-
}
|
|
1731
|
-
if (generatorPlugins.length > 0) if (this.inputNode) {
|
|
1732
|
-
const { timings, failed } = await this.#runGenerators(generatorPlugins, flushPending);
|
|
1733
|
-
await flushPending();
|
|
1734
|
-
for (const [name, duration] of timings) pluginTimings.set(name, duration);
|
|
1735
|
-
for (const entry of failed) failedPlugins.add(entry);
|
|
1736
|
-
} else for (const { plugin, hrStart } of generatorPlugins) {
|
|
1737
|
-
const duration = getElapsedMs(hrStart);
|
|
1738
|
-
pluginTimings.set(plugin.name, duration);
|
|
1739
|
-
await this.#emitPluginEnd({
|
|
1740
|
-
plugin,
|
|
1741
|
-
duration,
|
|
1742
|
-
success: true
|
|
2338
|
+
diagnostics.push(...await this.#runGenerators(generatorPlugins, () => processor.flush()));
|
|
2339
|
+
await processor.drain();
|
|
2340
|
+
await hooks.emit("kubb:plugins:end", Object.assign({ config }, this.#filesPayload()));
|
|
2341
|
+
await processor.drain();
|
|
2342
|
+
await hooks.emit("kubb:build:end", {
|
|
2343
|
+
files: this.fileManager.files,
|
|
2344
|
+
config,
|
|
2345
|
+
outputDir: resolve(config.root, config.output.path)
|
|
1743
2346
|
});
|
|
2347
|
+
return { diagnostics: Diagnostics.dedupe(diagnostics) };
|
|
2348
|
+
} catch (caughtError) {
|
|
2349
|
+
diagnostics.push(Diagnostics.from(caughtError));
|
|
2350
|
+
return { diagnostics: Diagnostics.dedupe(diagnostics) };
|
|
2351
|
+
} finally {
|
|
2352
|
+
this.fileManager.hooks.off("upsert", onFileUpsert);
|
|
1744
2353
|
}
|
|
1745
|
-
|
|
1746
|
-
await flushPending();
|
|
1747
|
-
const files = this.fileManager.files;
|
|
1748
|
-
await hooks.emit("kubb:build:end", {
|
|
1749
|
-
files,
|
|
1750
|
-
config,
|
|
1751
|
-
outputDir: resolve(config.root, config.output.path)
|
|
1752
|
-
});
|
|
1753
|
-
return {
|
|
1754
|
-
failedPlugins,
|
|
1755
|
-
pluginTimings
|
|
1756
|
-
};
|
|
1757
|
-
} catch (caughtError) {
|
|
1758
|
-
return {
|
|
1759
|
-
failedPlugins,
|
|
1760
|
-
pluginTimings,
|
|
1761
|
-
error: caughtError
|
|
1762
|
-
};
|
|
1763
|
-
} finally {
|
|
1764
|
-
this.fileManager.setOnUpsert(null);
|
|
1765
|
-
}
|
|
2354
|
+
});
|
|
1766
2355
|
}
|
|
1767
2356
|
#filesPayload() {
|
|
1768
2357
|
const driver = this;
|
|
@@ -1782,10 +2371,36 @@ var KubbDriver = class KubbDriver {
|
|
|
1782
2371
|
config: this.config
|
|
1783
2372
|
}, this.#filesPayload()));
|
|
1784
2373
|
}
|
|
2374
|
+
/**
|
|
2375
|
+
* Streams schemas and operations through every plugin's generators. Each node is run
|
|
2376
|
+
* through the plugin's transformer (from `this.#transforms`) before the generator sees it,
|
|
2377
|
+
* so plugins stay isolated and the hot path stays per-node. Schemas run before operations
|
|
2378
|
+
* because the two passes share `flushPending` and the FileProcessor's event emitter.
|
|
2379
|
+
* A failing plugin contributes an error diagnostic so the rest of the build continues.
|
|
2380
|
+
* Every plugin also contributes a `timing` diagnostic.
|
|
2381
|
+
*
|
|
2382
|
+
* When `entries` is empty or `this.inputNode` is `null`, every entry still gets a
|
|
2383
|
+
* `kubb:plugin:end` so middleware listeners (the barrel writer and friends) complete.
|
|
2384
|
+
*/
|
|
1785
2385
|
async #runGenerators(entries, flushPending) {
|
|
1786
|
-
const
|
|
1787
|
-
|
|
1788
|
-
|
|
2386
|
+
const diagnostics = [];
|
|
2387
|
+
if (entries.length === 0) return diagnostics;
|
|
2388
|
+
if (!this.inputNode) {
|
|
2389
|
+
for (const { plugin, hrStart } of entries) {
|
|
2390
|
+
const duration = getElapsedMs(hrStart);
|
|
2391
|
+
diagnostics.push(Diagnostics.performance({
|
|
2392
|
+
plugin: plugin.name,
|
|
2393
|
+
duration
|
|
2394
|
+
}));
|
|
2395
|
+
await this.#emitPluginEnd({
|
|
2396
|
+
plugin,
|
|
2397
|
+
duration,
|
|
2398
|
+
success: true
|
|
2399
|
+
});
|
|
2400
|
+
}
|
|
2401
|
+
return diagnostics;
|
|
2402
|
+
}
|
|
2403
|
+
const transforms = this.#transforms;
|
|
1789
2404
|
const { schemas, operations } = this.inputNode;
|
|
1790
2405
|
const states = entries.map(({ plugin, context, hrStart }) => {
|
|
1791
2406
|
const { exclude, include, override } = plugin.options;
|
|
@@ -1815,7 +2430,7 @@ var KubbDriver = class KubbDriver {
|
|
|
1815
2430
|
if (pruningStates.length > 0) {
|
|
1816
2431
|
const allSchemas = [];
|
|
1817
2432
|
for await (const schema of schemas) allSchemas.push(schema);
|
|
1818
|
-
const includedOpsByState = new Map(pruningStates.map((
|
|
2433
|
+
const includedOpsByState = new Map(pruningStates.map((state) => [state, []]));
|
|
1819
2434
|
for await (const operation of operations) for (const state of pruningStates) {
|
|
1820
2435
|
const { exclude, include, override } = state.plugin.options;
|
|
1821
2436
|
if (state.generatorContext.resolver.resolveOptions(operation, {
|
|
@@ -1830,33 +2445,45 @@ var KubbDriver = class KubbDriver {
|
|
|
1830
2445
|
includedOpsByState.delete(state);
|
|
1831
2446
|
}
|
|
1832
2447
|
}
|
|
1833
|
-
const
|
|
2448
|
+
const resolveForPlugin = (state, node) => {
|
|
2449
|
+
const { plugin, generatorContext } = state;
|
|
2450
|
+
const transformedNode = transforms.applyTo(plugin.name, node);
|
|
2451
|
+
if (state.optionsAreStatic) return {
|
|
2452
|
+
transformedNode,
|
|
2453
|
+
options: plugin.options
|
|
2454
|
+
};
|
|
2455
|
+
const { exclude, include, override } = plugin.options;
|
|
2456
|
+
const options = generatorContext.resolver.resolveOptions(transformedNode, {
|
|
2457
|
+
options: plugin.options,
|
|
2458
|
+
exclude,
|
|
2459
|
+
include,
|
|
2460
|
+
override
|
|
2461
|
+
});
|
|
2462
|
+
if (options === null) return null;
|
|
2463
|
+
return {
|
|
2464
|
+
transformedNode,
|
|
2465
|
+
options
|
|
2466
|
+
};
|
|
2467
|
+
};
|
|
1834
2468
|
const dispatchNode = async (state, node, dispatch) => {
|
|
1835
2469
|
if (state.failed) return;
|
|
1836
2470
|
try {
|
|
1837
|
-
const
|
|
1838
|
-
|
|
2471
|
+
const resolved = resolveForPlugin(state, node);
|
|
2472
|
+
if (!resolved) return;
|
|
2473
|
+
const { transformedNode, options } = resolved;
|
|
1839
2474
|
if (dispatch.checkAllowedNames && state.allowedSchemaNames !== null && "name" in transformedNode && transformedNode.name && !state.allowedSchemaNames.has(transformedNode.name)) return;
|
|
1840
|
-
const { exclude, include, override } = plugin.options;
|
|
1841
|
-
const options = state.optionsAreStatic ? plugin.options : generatorContext.resolver.resolveOptions(transformedNode, {
|
|
1842
|
-
options: plugin.options,
|
|
1843
|
-
exclude,
|
|
1844
|
-
include,
|
|
1845
|
-
override
|
|
1846
|
-
});
|
|
1847
|
-
if (options === null) return;
|
|
1848
2475
|
const ctx = {
|
|
1849
|
-
...generatorContext,
|
|
2476
|
+
...state.generatorContext,
|
|
1850
2477
|
options
|
|
1851
2478
|
};
|
|
1852
|
-
for (const gen of generators) {
|
|
1853
|
-
const
|
|
1854
|
-
if (!
|
|
1855
|
-
const raw =
|
|
1856
|
-
const
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
2479
|
+
for (const gen of state.generators) {
|
|
2480
|
+
const run = gen[dispatch.method];
|
|
2481
|
+
if (!run) continue;
|
|
2482
|
+
const raw = run(transformedNode, ctx);
|
|
2483
|
+
const result = isPromise(raw) ? await raw : raw;
|
|
2484
|
+
const applied = this.dispatch({
|
|
2485
|
+
result,
|
|
2486
|
+
renderer: gen.renderer
|
|
1860
2487
|
});
|
|
1861
2488
|
if (isPromise(applied)) await applied;
|
|
1862
2489
|
}
|
|
@@ -1876,15 +2503,15 @@ var KubbDriver = class KubbDriver {
|
|
|
1876
2503
|
checkAllowedNames: false,
|
|
1877
2504
|
emit: emitsOperationHook ? (node, ctx) => this.hooks.emit("kubb:generate:operation", node, ctx) : null
|
|
1878
2505
|
};
|
|
1879
|
-
const needsCollectedOperations = this.hooks.listenerCount("kubb:generate:operations") > 0 || states.some((
|
|
2506
|
+
const needsCollectedOperations = this.hooks.listenerCount("kubb:generate:operations") > 0 || states.some((state) => state.generators.some((gen) => !!gen.operations));
|
|
1880
2507
|
const collectedOperations = needsCollectedOperations ? [] : void 0;
|
|
1881
|
-
await forBatches(schemas, (nodes) => Promise.all(nodes.flatMap((
|
|
2508
|
+
await forBatches(schemas, (nodes) => Promise.all(nodes.flatMap((node) => states.map((state) => dispatchNode(state, node, schemaDispatch)))), {
|
|
1882
2509
|
concurrency: 8,
|
|
1883
2510
|
flush: flushPending
|
|
1884
2511
|
});
|
|
1885
2512
|
await forBatches(operations, (nodes) => {
|
|
1886
2513
|
if (needsCollectedOperations) collectedOperations?.push(...nodes);
|
|
1887
|
-
return Promise.all(nodes.flatMap((
|
|
2514
|
+
return Promise.all(nodes.flatMap((node) => states.map((state) => dispatchNode(state, node, operationDispatch))));
|
|
1888
2515
|
}, {
|
|
1889
2516
|
concurrency: 8,
|
|
1890
2517
|
flush: flushPending
|
|
@@ -1896,23 +2523,17 @@ var KubbDriver = class KubbDriver {
|
|
|
1896
2523
|
...generatorContext,
|
|
1897
2524
|
options: plugin.options
|
|
1898
2525
|
};
|
|
1899
|
-
const
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
options: plugin.options,
|
|
1905
|
-
exclude,
|
|
1906
|
-
include,
|
|
1907
|
-
override
|
|
1908
|
-
}) !== null;
|
|
1909
|
-
});
|
|
2526
|
+
const pluginOperations = (collectedOperations ?? []).reduce((acc, node) => {
|
|
2527
|
+
const resolved = resolveForPlugin(state, node);
|
|
2528
|
+
if (resolved) acc.push(resolved.transformedNode);
|
|
2529
|
+
return acc;
|
|
2530
|
+
}, []);
|
|
1910
2531
|
for (const gen of generators) {
|
|
1911
2532
|
if (!gen.operations) continue;
|
|
1912
|
-
await
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
|
|
2533
|
+
const result = await gen.operations(pluginOperations, ctx);
|
|
2534
|
+
await this.dispatch({
|
|
2535
|
+
result,
|
|
2536
|
+
renderer: gen.renderer
|
|
1916
2537
|
});
|
|
1917
2538
|
}
|
|
1918
2539
|
await this.hooks.emit("kubb:generate:operations", pluginOperations, ctx);
|
|
@@ -1921,60 +2542,80 @@ var KubbDriver = class KubbDriver {
|
|
|
1921
2542
|
state.error = caughtError;
|
|
1922
2543
|
}
|
|
1923
2544
|
const duration = getElapsedMs(state.hrStart);
|
|
1924
|
-
timings.set(state.plugin.name, duration);
|
|
1925
2545
|
await this.#emitPluginEnd({
|
|
1926
2546
|
plugin: state.plugin,
|
|
1927
2547
|
duration,
|
|
1928
2548
|
success: !state.failed,
|
|
1929
2549
|
error: state.failed && state.error ? state.error : void 0
|
|
1930
2550
|
});
|
|
1931
|
-
if (state.failed && state.error)
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
});
|
|
1935
|
-
await this.hooks.emit("kubb:debug", {
|
|
1936
|
-
date: /* @__PURE__ */ new Date(),
|
|
1937
|
-
logs: [state.failed ? "✗ Plugin start failed" : `✓ Plugin started successfully (${formatMs(duration)})`]
|
|
2551
|
+
if (state.failed && state.error) diagnostics.push({
|
|
2552
|
+
...Diagnostics.from(state.error),
|
|
2553
|
+
plugin: state.plugin.name
|
|
1938
2554
|
});
|
|
2555
|
+
diagnostics.push(Diagnostics.performance({
|
|
2556
|
+
plugin: state.plugin.name,
|
|
2557
|
+
duration
|
|
2558
|
+
}));
|
|
1939
2559
|
}
|
|
1940
|
-
return
|
|
1941
|
-
timings,
|
|
1942
|
-
failed
|
|
1943
|
-
};
|
|
2560
|
+
return diagnostics;
|
|
1944
2561
|
}
|
|
1945
2562
|
/**
|
|
1946
|
-
*
|
|
1947
|
-
*
|
|
2563
|
+
* Stores whatever a generator method or `kubb:generate:*` hook returned.
|
|
2564
|
+
*
|
|
2565
|
+
* - An `Array<FileNode>` goes straight into `fileManager` via `upsert`.
|
|
2566
|
+
* - A renderer element runs through `renderer` (the renderer factory, e.g. JSX) and the
|
|
2567
|
+
* produced files go to `fileManager.upsert`.
|
|
2568
|
+
* - A falsy result is treated as a no-op. The generator wrote files itself via
|
|
2569
|
+
* `ctx.upsertFile`.
|
|
2570
|
+
*
|
|
2571
|
+
* Pass `renderer` when the result may be a renderer element. Generators that only return
|
|
2572
|
+
* `Array<FileNode>` do not need one.
|
|
2573
|
+
*/
|
|
2574
|
+
async dispatch({ result, renderer }) {
|
|
2575
|
+
try {
|
|
2576
|
+
var _usingCtx$1 = _usingCtx();
|
|
2577
|
+
if (!result) return;
|
|
2578
|
+
if (Array.isArray(result)) {
|
|
2579
|
+
this.fileManager.upsert(...result);
|
|
2580
|
+
return;
|
|
2581
|
+
}
|
|
2582
|
+
if (!renderer) return;
|
|
2583
|
+
const instance = _usingCtx$1.u(renderer());
|
|
2584
|
+
if (instance.stream) {
|
|
2585
|
+
for (const file of instance.stream(result)) this.fileManager.upsert(file);
|
|
2586
|
+
return;
|
|
2587
|
+
}
|
|
2588
|
+
await instance.render(result);
|
|
2589
|
+
this.fileManager.upsert(...instance.files);
|
|
2590
|
+
} catch (_) {
|
|
2591
|
+
_usingCtx$1.e = _;
|
|
2592
|
+
} finally {
|
|
2593
|
+
_usingCtx$1.d();
|
|
2594
|
+
}
|
|
2595
|
+
}
|
|
2596
|
+
/**
|
|
2597
|
+
* Removes every listener the driver added. Listeners attached directly to `hooks` from outside
|
|
2598
|
+
* the driver survive. Called at the end of a build to prevent leaks across repeated builds.
|
|
1948
2599
|
*
|
|
1949
2600
|
* @internal
|
|
1950
2601
|
*/
|
|
1951
2602
|
dispose() {
|
|
1952
|
-
|
|
1953
|
-
this.#hookListeners.clear();
|
|
2603
|
+
this.#registry.dispose();
|
|
1954
2604
|
this.#eventGeneratorPlugins.clear();
|
|
2605
|
+
this.#transforms.dispose();
|
|
1955
2606
|
this.#resolvers.clear();
|
|
1956
2607
|
this.#defaultResolvers.clear();
|
|
1957
2608
|
this.fileManager.dispose();
|
|
1958
|
-
this.#fileProcessor.dispose();
|
|
1959
2609
|
this.inputNode = null;
|
|
1960
2610
|
this.#studio = {
|
|
1961
2611
|
source: null,
|
|
1962
2612
|
isOpen: false,
|
|
1963
2613
|
inputNode: null
|
|
1964
2614
|
};
|
|
1965
|
-
for (const [event, handler] of this.#middlewareListeners) this.hooks.off(event, handler);
|
|
1966
2615
|
}
|
|
1967
2616
|
[Symbol.dispose]() {
|
|
1968
2617
|
this.dispose();
|
|
1969
2618
|
}
|
|
1970
|
-
#trackHookListener(event, handler) {
|
|
1971
|
-
let handlers = this.#hookListeners.get(event);
|
|
1972
|
-
if (!handlers) {
|
|
1973
|
-
handlers = /* @__PURE__ */ new Set();
|
|
1974
|
-
this.#hookListeners.set(event, handlers);
|
|
1975
|
-
}
|
|
1976
|
-
handlers.add(handler);
|
|
1977
|
-
}
|
|
1978
2619
|
#getDefaultResolver = memoize(this.#defaultResolvers, (pluginName) => defineResolver(() => ({
|
|
1979
2620
|
name: "default",
|
|
1980
2621
|
pluginName
|
|
@@ -1998,6 +2639,21 @@ var KubbDriver = class KubbDriver {
|
|
|
1998
2639
|
}
|
|
1999
2640
|
getContext(plugin) {
|
|
2000
2641
|
const driver = this;
|
|
2642
|
+
const report = (diagnostic) => {
|
|
2643
|
+
Diagnostics.report({
|
|
2644
|
+
...diagnostic,
|
|
2645
|
+
plugin: plugin.name
|
|
2646
|
+
});
|
|
2647
|
+
if (diagnostic.severity === "error") {
|
|
2648
|
+
driver.hooks.emit("kubb:error", { error: diagnostic.cause ?? new Error(diagnostic.message) });
|
|
2649
|
+
return;
|
|
2650
|
+
}
|
|
2651
|
+
if (diagnostic.severity === "warning") {
|
|
2652
|
+
driver.hooks.emit("kubb:warn", { message: diagnostic.message });
|
|
2653
|
+
return;
|
|
2654
|
+
}
|
|
2655
|
+
driver.hooks.emit("kubb:info", { message: diagnostic.message });
|
|
2656
|
+
};
|
|
2001
2657
|
return {
|
|
2002
2658
|
config: driver.config,
|
|
2003
2659
|
get root() {
|
|
@@ -2031,21 +2687,47 @@ var KubbDriver = class KubbDriver {
|
|
|
2031
2687
|
return driver.getResolver(plugin.name);
|
|
2032
2688
|
},
|
|
2033
2689
|
get transformer() {
|
|
2034
|
-
return plugin.
|
|
2690
|
+
return driver.#transforms.get(plugin.name);
|
|
2035
2691
|
},
|
|
2036
2692
|
warn(message) {
|
|
2037
|
-
|
|
2693
|
+
report({
|
|
2694
|
+
code: diagnosticCode.pluginWarning,
|
|
2695
|
+
severity: "warning",
|
|
2696
|
+
message
|
|
2697
|
+
});
|
|
2038
2698
|
},
|
|
2039
2699
|
error(error) {
|
|
2040
|
-
|
|
2700
|
+
const cause = typeof error === "string" ? void 0 : error;
|
|
2701
|
+
report({
|
|
2702
|
+
code: diagnosticCode.pluginFailed,
|
|
2703
|
+
severity: "error",
|
|
2704
|
+
message: typeof error === "string" ? error : error.message,
|
|
2705
|
+
cause
|
|
2706
|
+
});
|
|
2041
2707
|
},
|
|
2042
2708
|
info(message) {
|
|
2043
|
-
|
|
2709
|
+
report({
|
|
2710
|
+
code: diagnosticCode.pluginInfo,
|
|
2711
|
+
severity: "info",
|
|
2712
|
+
message
|
|
2713
|
+
});
|
|
2044
2714
|
},
|
|
2045
2715
|
async openInStudio(options) {
|
|
2046
2716
|
if (!driver.config.devtools || driver.#studio.isOpen) return;
|
|
2047
|
-
if (typeof driver.config.devtools !== "object") throw new
|
|
2048
|
-
|
|
2717
|
+
if (typeof driver.config.devtools !== "object") throw new DiagnosticError({
|
|
2718
|
+
code: diagnosticCode.devtoolsInvalid,
|
|
2719
|
+
severity: "error",
|
|
2720
|
+
message: "The `devtools` config must be an object.",
|
|
2721
|
+
help: "Set `devtools` to an options object, or remove it to disable Kubb Studio.",
|
|
2722
|
+
location: { kind: "config" }
|
|
2723
|
+
});
|
|
2724
|
+
if (!driver.adapter || !driver.#studio.source) throw new DiagnosticError({
|
|
2725
|
+
code: diagnosticCode.adapterRequired,
|
|
2726
|
+
severity: "error",
|
|
2727
|
+
message: "An adapter is required to open Kubb Studio, but none is configured.",
|
|
2728
|
+
help: "Set `adapter` in kubb.config.ts (for example `adapterOas()`).",
|
|
2729
|
+
location: { kind: "config" }
|
|
2730
|
+
});
|
|
2049
2731
|
driver.#studio.isOpen = true;
|
|
2050
2732
|
const studioUrl = driver.config.devtools?.studioUrl ?? "https://kubb.studio";
|
|
2051
2733
|
driver.#studio.inputNode ??= Promise.resolve(driver.adapter.parse(driver.#studio.source));
|
|
@@ -2058,59 +2740,25 @@ var KubbDriver = class KubbDriver {
|
|
|
2058
2740
|
}
|
|
2059
2741
|
requirePlugin(pluginName) {
|
|
2060
2742
|
const plugin = this.plugins.get(pluginName);
|
|
2061
|
-
if (!plugin) throw new
|
|
2743
|
+
if (!plugin) throw new DiagnosticError({
|
|
2744
|
+
code: diagnosticCode.pluginNotFound,
|
|
2745
|
+
severity: "error",
|
|
2746
|
+
message: `Plugin "${pluginName}" is required but not found. Make sure it is included in your Kubb config.`,
|
|
2747
|
+
help: `Add "${pluginName}" to the \`plugins\` array in kubb.config.ts, or remove the dependency on it.`,
|
|
2748
|
+
location: { kind: "config" }
|
|
2749
|
+
});
|
|
2062
2750
|
return plugin;
|
|
2063
2751
|
}
|
|
2064
2752
|
};
|
|
2065
|
-
/**
|
|
2066
|
-
* Handles the return value of a plugin AST hook or generator method.
|
|
2067
|
-
*
|
|
2068
|
-
* - Renderer output → rendered via the provided `rendererFactory` (e.g. JSX), files stored in `driver.fileManager`
|
|
2069
|
-
* - `Array<FileNode>` → added directly into `driver.fileManager`
|
|
2070
|
-
* - `void` / `null` / `undefined` → no-op (plugin handled it via `this.upsertFile`)
|
|
2071
|
-
*
|
|
2072
|
-
* Pass a `rendererFactory` (e.g. `jsxRenderer` from `@kubb/renderer-jsx`) when the result
|
|
2073
|
-
* may be a renderer element. Generators that only return `Array<FileNode>` do not need one.
|
|
2074
|
-
*/
|
|
2075
|
-
function applyHookResult({ result, driver, rendererFactory }) {
|
|
2076
|
-
if (!result) return;
|
|
2077
|
-
if (Array.isArray(result)) {
|
|
2078
|
-
driver.fileManager.upsert(...result);
|
|
2079
|
-
return;
|
|
2080
|
-
}
|
|
2081
|
-
if (!rendererFactory) return;
|
|
2082
|
-
const renderer = rendererFactory();
|
|
2083
|
-
if (renderer.stream) try {
|
|
2084
|
-
var _usingCtx$1 = _usingCtx();
|
|
2085
|
-
const r = _usingCtx$1.u(renderer);
|
|
2086
|
-
for (const file of r.stream(result)) driver.fileManager.upsert(file);
|
|
2087
|
-
return;
|
|
2088
|
-
} catch (_) {
|
|
2089
|
-
_usingCtx$1.e = _;
|
|
2090
|
-
} finally {
|
|
2091
|
-
_usingCtx$1.d();
|
|
2092
|
-
}
|
|
2093
|
-
return applyAsyncRender({
|
|
2094
|
-
renderer,
|
|
2095
|
-
result,
|
|
2096
|
-
driver
|
|
2097
|
-
});
|
|
2098
|
-
}
|
|
2099
|
-
async function applyAsyncRender({ renderer, result, driver }) {
|
|
2100
|
-
try {
|
|
2101
|
-
var _usingCtx3 = _usingCtx();
|
|
2102
|
-
const r = _usingCtx3.u(renderer);
|
|
2103
|
-
await r.render(result);
|
|
2104
|
-
driver.fileManager.upsert(...r.files);
|
|
2105
|
-
} catch (_) {
|
|
2106
|
-
_usingCtx3.e = _;
|
|
2107
|
-
} finally {
|
|
2108
|
-
_usingCtx3.d();
|
|
2109
|
-
}
|
|
2110
|
-
}
|
|
2111
2753
|
function inputToAdapterSource(config) {
|
|
2112
2754
|
const input = config.input;
|
|
2113
|
-
if (!input) throw new
|
|
2755
|
+
if (!input) throw new DiagnosticError({
|
|
2756
|
+
code: diagnosticCode.inputRequired,
|
|
2757
|
+
severity: "error",
|
|
2758
|
+
message: "An adapter is configured without an input.",
|
|
2759
|
+
help: "Provide `input.path` (a file or URL) or `input.data` (an inline spec) in your Kubb config.",
|
|
2760
|
+
location: { kind: "config" }
|
|
2761
|
+
});
|
|
2114
2762
|
if ("data" in input) return {
|
|
2115
2763
|
type: "data",
|
|
2116
2764
|
data: input.data
|
|
@@ -2125,6 +2773,6 @@ function inputToAdapterSource(config) {
|
|
|
2125
2773
|
};
|
|
2126
2774
|
}
|
|
2127
2775
|
//#endregion
|
|
2128
|
-
export {
|
|
2776
|
+
export { URLPath as _, defineResolver as a, Diagnostics as c, isProblemDiagnostic as d, isUpdateDiagnostic as f, logLevel as g, diagnosticCode as h, FileManager as i, diagnosticCatalog as l, DEFAULT_STUDIO_URL as m, _usingCtx as n, definePlugin as o, narrowDiagnostic as p, FileProcessor as r, DiagnosticError as s, KubbDriver as t, isPerformanceDiagnostic as u, AsyncEventEmitter as v, BuildError as y };
|
|
2129
2777
|
|
|
2130
|
-
//# sourceMappingURL=KubbDriver-
|
|
2778
|
+
//# sourceMappingURL=KubbDriver-CckeYpMG.js.map
|