@kubb/adapter-oas 5.14.4 → 5.14.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -23,6 +23,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
23
23
  //#endregion
24
24
  let _kubb_core = require("@kubb/core");
25
25
  let _kubb_kit = require("@kubb/kit");
26
+ let node_path = require("node:path");
26
27
  let node_fs_promises = require("node:fs/promises");
27
28
  let _scalar_openapi_upgrader = require("@scalar/openapi-upgrader");
28
29
  let api_ref_bundler = require("api-ref-bundler");
@@ -36,7 +37,7 @@ var compatibility = {
36
37
  };
37
38
  //#endregion
38
39
  //#region package.json
39
- var version = "5.14.4";
40
+ var version = "5.14.6";
40
41
  //#endregion
41
42
  //#region src/constants.ts
42
43
  /**
@@ -714,6 +715,30 @@ function inlinePathRefs(node, resolve, visiting = []) {
714
715
  for (const [key, value] of Object.entries(node)) node[key] = inlinePathRefs(value, resolve, visiting);
715
716
  return node;
716
717
  }
718
+ const namedEntryKeys = /* @__PURE__ */ new Set([
719
+ "properties",
720
+ "patternProperties",
721
+ "headers",
722
+ "schemas",
723
+ "responses",
724
+ "parameters",
725
+ "requestBodies",
726
+ "examples",
727
+ "links",
728
+ "callbacks",
729
+ "securitySchemes",
730
+ "securityDefinitions",
731
+ "pathItems",
732
+ "definitions",
733
+ "$defs"
734
+ ]);
735
+ /**
736
+ * True when `path` runs through an `x-` vendor extension. Kubb never reads an extension, so a `$ref`
737
+ * Kubb cannot read inside one cannot change the output.
738
+ */
739
+ function isExtensionPath(path) {
740
+ return path.some((token, index) => String(token).startsWith("x-") && !namedEntryKeys.has(String(path[index - 1])));
741
+ }
717
742
  /**
718
743
  * Bundles a multi-file OpenAPI document into a single document via `api-ref-bundler`.
719
744
  *
@@ -727,33 +752,55 @@ function inlinePathRefs(node, resolve, visiting = []) {
727
752
  * all-internal document it is a no-op that still walks the whole tree to confirm that, which
728
753
  * costs real time on a large spec.
729
754
  *
755
+ * `document` stands in for the file at `pathOrUrl`, such as inline input. Every other file or URL
756
+ * read, including one that fails, lands in `sources`. A `$ref` that cannot be read reports
757
+ * `KUBB_REF_NOT_FOUND` at the key that holds it, as a warning when it sits inside an `x-` extension.
758
+ *
730
759
  * @example Local file
731
760
  * `const document = await bundleDocument('./openapi.yaml')`
732
761
  *
733
762
  * @example Remote URL
734
763
  * `const document = await bundleDocument('https://example.com/openapi.yaml')`
735
764
  */
736
- async function bundleDocument(pathOrUrl) {
765
+ async function bundleDocument(pathOrUrl, { document, sources = [] } = {}) {
737
766
  const cache = /* @__PURE__ */ new Map();
767
+ const toKey = (sourcePath) => urlRegExp.test(sourcePath) ? new URL(sourcePath).href : sourcePath;
738
768
  const resolver = (sourcePath) => {
739
- const key = urlRegExp.test(sourcePath) ? new URL(sourcePath).href : sourcePath;
769
+ if (document && sourcePath === pathOrUrl) return Promise.resolve(document);
770
+ const key = toKey(sourcePath);
740
771
  const cached = cache.get(key);
741
772
  if (cached) return cached;
773
+ sources.push(key);
742
774
  const result = resolveSource(sourcePath);
743
775
  cache.set(key, result);
744
776
  return result;
745
777
  };
746
778
  const root = await resolver(pathOrUrl);
747
779
  if (typeof root === "object" && root !== null && !hasExternalRef(root)) return root;
748
- return await (0, api_ref_bundler.bundle)(pathOrUrl, resolver);
780
+ return await (0, api_ref_bundler.bundle)(pathOrUrl, resolver, { hooks: { onError: (message, { path, state }) => {
781
+ const pointer = toKey(state.baseFile) === toKey(pathOrUrl) ? `#/${path.map((token) => escapePointerToken(String(token))).join("/")}` : void 0;
782
+ const diagnostic = {
783
+ code: _kubb_kit.Diagnostics.code.refNotFound,
784
+ severity: isExtensionPath(path) ? "warning" : "error",
785
+ message: `Could not bundle an external \`$ref\`. ${message}.`,
786
+ help: "Check that the file or URL the `$ref` points at exists and is readable, or fix the `$ref`.",
787
+ ...pointer && { location: {
788
+ kind: "schema",
789
+ pointer
790
+ } }
791
+ };
792
+ if (!_kubb_kit.Diagnostics.report(diagnostic) && diagnostic.severity === "error") throw new _kubb_kit.Diagnostics.Error(diagnostic);
793
+ } } });
749
794
  }
795
+ const inlineInputName = "<inline input>";
750
796
  /**
751
797
  * Creates a `Document` from an `AdapterSource`.
752
798
  *
753
799
  * - `{ type: 'path' }` bundles an absolute file path (core resolves `input`) or remote URL via
754
800
  * `api-ref-bundler`, hoisting external file schemas into named `components.schemas` entries so
755
801
  * generators can emit named types and imports.
756
- * - `{ type: 'data' }` parses an inline string (YAML/JSON) or raw object.
802
+ * - `{ type: 'data' }` parses an inline string (YAML/JSON) or raw object, then bundles its
803
+ * external `$ref`s from `root` when it has any. Each file or URL read lands in `sources`.
757
804
  *
758
805
  * Swagger 2.0 and OpenAPI 3.0 documents are up-converted to OpenAPI 3.1 via `@scalar/openapi-upgrader`.
759
806
  *
@@ -763,14 +810,20 @@ async function bundleDocument(pathOrUrl) {
763
810
  * const document = await parseFromConfig({ type: 'data', data: '{"openapi":"3.0.0",...}' })
764
811
  * ```
765
812
  */
766
- async function parseFromConfig(source) {
813
+ async function parseFromConfig(source, sources = []) {
767
814
  const raw = await (async () => {
768
- if (source.type === "data") return typeof source.data === "string" ? parseYaml(source.data, {
769
- origin: "the inline `input` string",
770
- help: "Fix the syntax at the reported line. Kubb reads `input` as an inline spec, not a file path, because it starts with `{`, `[`, `openapi:`, or `swagger:`, or spans several lines."
771
- }) : structuredClone(source.data);
815
+ if (source.type === "data") {
816
+ const data = typeof source.data === "string" ? parseYaml(source.data, {
817
+ origin: "the inline `input` string",
818
+ help: "Fix the syntax at the reported line. Kubb reads `input` as an inline spec, not a file path, because it starts with `{`, `[`, `openapi:`, or `swagger:`, or spans several lines."
819
+ }) : structuredClone(source.data);
820
+ return source.root && hasExternalRef(data) ? bundleDocument((0, node_path.join)(source.root, inlineInputName), {
821
+ document: data,
822
+ sources
823
+ }) : data;
824
+ }
772
825
  await assertInputExists(source.path);
773
- return bundleDocument(source.path);
826
+ return bundleDocument(source.path, { sources });
774
827
  })();
775
828
  if (raw && typeof raw === "object") {
776
829
  const document = raw;
@@ -2905,6 +2958,7 @@ const adapterOas = (0, _kubb_core.defineExtension)({
2905
2958
  }),
2906
2959
  build({ validate, contentType, server, discriminator, enums, ...parserOptions }) {
2907
2960
  let parsedDocument = null;
2961
+ let sources = [];
2908
2962
  const pendingValidations = [];
2909
2963
  const inputCache = /* @__PURE__ */ new WeakMap();
2910
2964
  /**
@@ -3038,6 +3092,9 @@ const adapterOas = (0, _kubb_core.defineExtension)({
3038
3092
  get document() {
3039
3093
  return parsedDocument;
3040
3094
  },
3095
+ get sources() {
3096
+ return sources;
3097
+ },
3041
3098
  locate,
3042
3099
  async validate(input, options) {
3043
3100
  const document = await parseFromConfig({
@@ -3053,9 +3110,14 @@ const adapterOas = (0, _kubb_core.defineExtension)({
3053
3110
  async parse(source, { signal } = {}) {
3054
3111
  signal?.throwIfAborted();
3055
3112
  const cached = inputCache.get(source);
3056
- if (cached) return cached;
3113
+ if (cached) {
3114
+ sources = cached.sources;
3115
+ return cached.promise;
3116
+ }
3117
+ const read = [];
3118
+ sources = read;
3057
3119
  const promise = (async () => {
3058
- const document = await parseFromConfig(source);
3120
+ const document = await parseFromConfig(source, read);
3059
3121
  signal?.throwIfAborted();
3060
3122
  assertDocument(document);
3061
3123
  if (validate) pendingValidations.push(validateDocumentInWorker(document));
@@ -3075,7 +3137,10 @@ const adapterOas = (0, _kubb_core.defineExtension)({
3075
3137
  })
3076
3138
  });
3077
3139
  })();
3078
- inputCache.set(source, promise);
3140
+ inputCache.set(source, {
3141
+ promise,
3142
+ sources: read
3143
+ });
3079
3144
  return promise.catch((error) => {
3080
3145
  if (signal?.aborted) inputCache.delete(source);
3081
3146
  throw error;