@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.js CHANGED
@@ -1,5 +1,6 @@
1
1
  import { defineExtension } from "@kubb/core";
2
2
  import { Diagnostics, ast, childName, enumPrimitive, enumPropName, extractRefName, macroDiscriminatorEnum, macroEnumName, macroSimplifyUnion, mergeAdjacentObjectsLazy } from "@kubb/kit";
3
+ import { join } from "node:path";
3
4
  import { access, readFile } from "node:fs/promises";
4
5
  import { upgrade } from "@scalar/openapi-upgrader";
5
6
  import { bundle } from "api-ref-bundler";
@@ -20,7 +21,7 @@ var compatibility = {
20
21
  };
21
22
  //#endregion
22
23
  //#region package.json
23
- var version = "5.14.4";
24
+ var version = "5.14.6";
24
25
  //#endregion
25
26
  //#region src/constants.ts
26
27
  /**
@@ -698,6 +699,30 @@ function inlinePathRefs(node, resolve, visiting = []) {
698
699
  for (const [key, value] of Object.entries(node)) node[key] = inlinePathRefs(value, resolve, visiting);
699
700
  return node;
700
701
  }
702
+ const namedEntryKeys = /* @__PURE__ */ new Set([
703
+ "properties",
704
+ "patternProperties",
705
+ "headers",
706
+ "schemas",
707
+ "responses",
708
+ "parameters",
709
+ "requestBodies",
710
+ "examples",
711
+ "links",
712
+ "callbacks",
713
+ "securitySchemes",
714
+ "securityDefinitions",
715
+ "pathItems",
716
+ "definitions",
717
+ "$defs"
718
+ ]);
719
+ /**
720
+ * True when `path` runs through an `x-` vendor extension. Kubb never reads an extension, so a `$ref`
721
+ * Kubb cannot read inside one cannot change the output.
722
+ */
723
+ function isExtensionPath(path) {
724
+ return path.some((token, index) => String(token).startsWith("x-") && !namedEntryKeys.has(String(path[index - 1])));
725
+ }
701
726
  /**
702
727
  * Bundles a multi-file OpenAPI document into a single document via `api-ref-bundler`.
703
728
  *
@@ -711,33 +736,55 @@ function inlinePathRefs(node, resolve, visiting = []) {
711
736
  * all-internal document it is a no-op that still walks the whole tree to confirm that, which
712
737
  * costs real time on a large spec.
713
738
  *
739
+ * `document` stands in for the file at `pathOrUrl`, such as inline input. Every other file or URL
740
+ * read, including one that fails, lands in `sources`. A `$ref` that cannot be read reports
741
+ * `KUBB_REF_NOT_FOUND` at the key that holds it, as a warning when it sits inside an `x-` extension.
742
+ *
714
743
  * @example Local file
715
744
  * `const document = await bundleDocument('./openapi.yaml')`
716
745
  *
717
746
  * @example Remote URL
718
747
  * `const document = await bundleDocument('https://example.com/openapi.yaml')`
719
748
  */
720
- async function bundleDocument(pathOrUrl) {
749
+ async function bundleDocument(pathOrUrl, { document, sources = [] } = {}) {
721
750
  const cache = /* @__PURE__ */ new Map();
751
+ const toKey = (sourcePath) => urlRegExp.test(sourcePath) ? new URL(sourcePath).href : sourcePath;
722
752
  const resolver = (sourcePath) => {
723
- const key = urlRegExp.test(sourcePath) ? new URL(sourcePath).href : sourcePath;
753
+ if (document && sourcePath === pathOrUrl) return Promise.resolve(document);
754
+ const key = toKey(sourcePath);
724
755
  const cached = cache.get(key);
725
756
  if (cached) return cached;
757
+ sources.push(key);
726
758
  const result = resolveSource(sourcePath);
727
759
  cache.set(key, result);
728
760
  return result;
729
761
  };
730
762
  const root = await resolver(pathOrUrl);
731
763
  if (typeof root === "object" && root !== null && !hasExternalRef(root)) return root;
732
- return await bundle(pathOrUrl, resolver);
764
+ return await bundle(pathOrUrl, resolver, { hooks: { onError: (message, { path, state }) => {
765
+ const pointer = toKey(state.baseFile) === toKey(pathOrUrl) ? `#/${path.map((token) => escapePointerToken(String(token))).join("/")}` : void 0;
766
+ const diagnostic = {
767
+ code: Diagnostics.code.refNotFound,
768
+ severity: isExtensionPath(path) ? "warning" : "error",
769
+ message: `Could not bundle an external \`$ref\`. ${message}.`,
770
+ help: "Check that the file or URL the `$ref` points at exists and is readable, or fix the `$ref`.",
771
+ ...pointer && { location: {
772
+ kind: "schema",
773
+ pointer
774
+ } }
775
+ };
776
+ if (!Diagnostics.report(diagnostic) && diagnostic.severity === "error") throw new Diagnostics.Error(diagnostic);
777
+ } } });
733
778
  }
779
+ const inlineInputName = "<inline input>";
734
780
  /**
735
781
  * Creates a `Document` from an `AdapterSource`.
736
782
  *
737
783
  * - `{ type: 'path' }` bundles an absolute file path (core resolves `input`) or remote URL via
738
784
  * `api-ref-bundler`, hoisting external file schemas into named `components.schemas` entries so
739
785
  * generators can emit named types and imports.
740
- * - `{ type: 'data' }` parses an inline string (YAML/JSON) or raw object.
786
+ * - `{ type: 'data' }` parses an inline string (YAML/JSON) or raw object, then bundles its
787
+ * external `$ref`s from `root` when it has any. Each file or URL read lands in `sources`.
741
788
  *
742
789
  * Swagger 2.0 and OpenAPI 3.0 documents are up-converted to OpenAPI 3.1 via `@scalar/openapi-upgrader`.
743
790
  *
@@ -747,14 +794,20 @@ async function bundleDocument(pathOrUrl) {
747
794
  * const document = await parseFromConfig({ type: 'data', data: '{"openapi":"3.0.0",...}' })
748
795
  * ```
749
796
  */
750
- async function parseFromConfig(source) {
797
+ async function parseFromConfig(source, sources = []) {
751
798
  const raw = await (async () => {
752
- if (source.type === "data") return typeof source.data === "string" ? parseYaml(source.data, {
753
- origin: "the inline `input` string",
754
- 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."
755
- }) : structuredClone(source.data);
799
+ if (source.type === "data") {
800
+ const data = typeof source.data === "string" ? parseYaml(source.data, {
801
+ origin: "the inline `input` string",
802
+ 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."
803
+ }) : structuredClone(source.data);
804
+ return source.root && hasExternalRef(data) ? bundleDocument(join(source.root, inlineInputName), {
805
+ document: data,
806
+ sources
807
+ }) : data;
808
+ }
756
809
  await assertInputExists(source.path);
757
- return bundleDocument(source.path);
810
+ return bundleDocument(source.path, { sources });
758
811
  })();
759
812
  if (raw && typeof raw === "object") {
760
813
  const document = raw;
@@ -2890,6 +2943,7 @@ const adapterOas = defineExtension({
2890
2943
  }),
2891
2944
  build({ validate, contentType, server, discriminator, enums, ...parserOptions }) {
2892
2945
  let parsedDocument = null;
2946
+ let sources = [];
2893
2947
  const pendingValidations = [];
2894
2948
  const inputCache = /* @__PURE__ */ new WeakMap();
2895
2949
  /**
@@ -3023,6 +3077,9 @@ const adapterOas = defineExtension({
3023
3077
  get document() {
3024
3078
  return parsedDocument;
3025
3079
  },
3080
+ get sources() {
3081
+ return sources;
3082
+ },
3026
3083
  locate,
3027
3084
  async validate(input, options) {
3028
3085
  const document = await parseFromConfig({
@@ -3038,9 +3095,14 @@ const adapterOas = defineExtension({
3038
3095
  async parse(source, { signal } = {}) {
3039
3096
  signal?.throwIfAborted();
3040
3097
  const cached = inputCache.get(source);
3041
- if (cached) return cached;
3098
+ if (cached) {
3099
+ sources = cached.sources;
3100
+ return cached.promise;
3101
+ }
3102
+ const read = [];
3103
+ sources = read;
3042
3104
  const promise = (async () => {
3043
- const document = await parseFromConfig(source);
3105
+ const document = await parseFromConfig(source, read);
3044
3106
  signal?.throwIfAborted();
3045
3107
  assertDocument(document);
3046
3108
  if (validate) pendingValidations.push(validateDocumentInWorker(document));
@@ -3060,7 +3122,10 @@ const adapterOas = defineExtension({
3060
3122
  })
3061
3123
  });
3062
3124
  })();
3063
- inputCache.set(source, promise);
3125
+ inputCache.set(source, {
3126
+ promise,
3127
+ sources: read
3128
+ });
3064
3129
  return promise.catch((error) => {
3065
3130
  if (signal?.aborted) inputCache.delete(source);
3066
3131
  throw error;