@kubb/oas 4.27.2 → 4.27.4

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kubb/oas",
3
- "version": "4.27.2",
3
+ "version": "4.27.4",
4
4
  "description": "OpenAPI Specification (OAS) utilities and helpers for Kubb, providing parsing, normalization, and manipulation of OpenAPI/Swagger schemas.",
5
5
  "keywords": [
6
6
  "openapi",
@@ -53,14 +53,14 @@
53
53
  }
54
54
  ],
55
55
  "dependencies": {
56
- "@redocly/openapi-core": "^2.19.1",
56
+ "@readme/openapi-parser": "^5.5.0",
57
57
  "jsonpointer": "^5.0.1",
58
58
  "oas": "^28.9.0",
59
59
  "oas-normalize": "^15.7.1",
60
60
  "openapi-types": "^12.1.3",
61
61
  "remeda": "^2.33.6",
62
62
  "swagger2openapi": "^7.0.8",
63
- "@kubb/core": "4.27.2"
63
+ "@kubb/core": "4.27.4"
64
64
  },
65
65
  "devDependencies": {
66
66
  "@stoplight/yaml": "^4.3.0",
package/src/index.ts CHANGED
@@ -1,4 +1,3 @@
1
- export { findSchemaDefinition, matchesMimeType } from 'oas/utils'
2
1
  export { Oas } from './Oas.ts'
3
2
  export * from './types.ts'
4
3
  export {
package/src/utils.spec.ts CHANGED
@@ -82,6 +82,75 @@ components:
82
82
  )
83
83
  expect(oas.api?.info.title).toBe('Swagger PetStore')
84
84
  })
85
+
86
+ test('parse a spec with an external file $ref resolves the ref via bundling', async () => {
87
+ const specPath = path.resolve(__dirname, '../mocks/petStoreExternalFileRef.yaml')
88
+ const oas = await parse(specPath)
89
+
90
+ expect(oas).toBeDefined()
91
+ expect(oas.api?.info.title).toBe('PetStore with external file ref')
92
+ // After bundling, the external file $ref is resolved: the Category schema is inlined
93
+ const petSchema = (oas.api as any).components?.schemas?.Pet
94
+ expect(petSchema).toBeDefined()
95
+ expect(petSchema.type).toBe('object')
96
+ expect(petSchema.required).toEqual(['id', 'name'])
97
+ expect(petSchema.properties.id).toEqual({ type: 'integer', format: 'int64' })
98
+ expect(petSchema.properties.name).toEqual({ type: 'string' })
99
+ // The external file $ref is resolved: category has the inline schema from category.yaml
100
+ const category = petSchema.properties.category
101
+ expect(category).toBeDefined()
102
+ expect(category.$ref).toBeUndefined()
103
+ expect(category.type).toBe('object')
104
+ expect(category.properties.id).toEqual({ type: 'integer', format: 'int64' })
105
+ })
106
+
107
+ test('parse a spec with an external URL $ref resolves the ref via bundling', async () => {
108
+ const specPath = path.resolve(__dirname, '../../plugin-ts/mocks/petStore.yaml')
109
+ const oas = await parse(specPath)
110
+
111
+ expect(oas).toBeDefined()
112
+ expect(oas.api?.info.title).toBe('Swagger PetStore')
113
+ const petSchema = (oas.api as any).components?.schemas?.Pet
114
+ expect(petSchema).toBeDefined()
115
+ expect(petSchema.type).toBe('object')
116
+ expect(petSchema.required).toEqual(['id', 'name'])
117
+ expect(petSchema.properties.id).toEqual({ type: 'integer', format: 'int64' })
118
+ expect(petSchema.properties.name).toEqual({ type: 'string' })
119
+ expect(petSchema.properties.tag).toEqual({ type: 'string' })
120
+ // After bundling the URL $ref is resolved (inlined) when network is available;
121
+ // on network failure the fallback plain load preserves the original $ref pointer.
122
+ expect(petSchema.properties.category).toBeDefined()
123
+ })
124
+
125
+ test('dereference a spec with external $refs works after bundling', async () => {
126
+ const specPath = path.resolve(__dirname, '../mocks/petStoreExternalFileRef.yaml')
127
+ const oas = await parse(specPath)
128
+
129
+ // After bundling, external file refs are resolved inline so dereference() should work without issues
130
+ await expect(oas.dereference()).resolves.not.toThrow()
131
+
132
+ const petSchema = (oas.api as any).components?.schemas?.Pet
133
+ expect(petSchema).toBeDefined()
134
+ expect(petSchema.type).toBe('object')
135
+ // After bundling, the category is inlined (no external $ref remaining)
136
+ const category = petSchema.properties.category
137
+ expect(category).toBeDefined()
138
+ expect(category.$ref).toBeUndefined()
139
+ expect(category.type).toBe('object')
140
+ })
141
+
142
+ test('dereference a spec with an external URL $ref works after bundling', async () => {
143
+ const specPath = path.resolve(__dirname, '../../plugin-ts/mocks/petStore.yaml')
144
+ const oas = await parse(specPath)
145
+
146
+ // After bundling (or fallback to load on network error), dereference() should work
147
+ await expect(oas.dereference()).resolves.not.toThrow()
148
+
149
+ const petSchema = (oas.api as any).components?.schemas?.Pet
150
+ expect(petSchema).toBeDefined()
151
+ expect(petSchema.type).toBe('object')
152
+ expect(petSchema.properties.category).toBeDefined()
153
+ })
85
154
  })
86
155
 
87
156
  describe('parseFromConfig', () => {
package/src/utils.ts CHANGED
@@ -2,7 +2,7 @@ import path from 'node:path'
2
2
  import type { Config } from '@kubb/core'
3
3
  import { pascalCase } from '@kubb/core/transformers'
4
4
  import { URLPath } from '@kubb/core/utils'
5
- import { bundle, loadConfig } from '@redocly/openapi-core'
5
+ import { bundle } from '@readme/openapi-parser'
6
6
  import yaml from '@stoplight/yaml'
7
7
  import type { ParameterObject, SchemaObject } from 'oas/types'
8
8
  import { isRef, isSchema } from 'oas/types'
@@ -163,14 +163,21 @@ export function getDefaultValue(schema?: SchemaObject): string | undefined {
163
163
 
164
164
  export async function parse(
165
165
  pathOrApi: string | Document,
166
- { oasClass = Oas, canBundle = true, enablePaths = true }: { oasClass?: typeof Oas; canBundle?: boolean; enablePaths?: boolean } = {},
166
+ { oasClass = Oas, enablePaths = true }: { oasClass?: typeof Oas; canBundle?: boolean; enablePaths?: boolean } = {},
167
167
  ): Promise<Oas> {
168
- if (typeof pathOrApi === 'string' && canBundle) {
169
- // resolve external refs
170
- const config = await loadConfig()
171
- const bundleResults = await bundle({ ref: pathOrApi, config, base: pathOrApi })
172
-
173
- return parse(bundleResults.bundle.parsed as string, { oasClass, canBundle, enablePaths })
168
+ // Only attempt to bundle when pathOrApi is a file path or URL (not inline YAML/JSON strings).
169
+ // Inline content (YAML strings with newlines, JSON strings starting with '{') is handled by plain load.
170
+ const isPathOrUrl = typeof pathOrApi === 'string' && !pathOrApi.match(/\n/) && !pathOrApi.match(/^\s*\{/)
171
+ if (isPathOrUrl && enablePaths) {
172
+ // Bundle the spec using the string path directly so that relative external $refs
173
+ // (file and URL) are resolved with the correct base path context.
174
+ try {
175
+ const bundled = await bundle(pathOrApi)
176
+ return parse(bundled as Document, { oasClass, enablePaths })
177
+ } catch (e) {
178
+ // If bundling fails (e.g. unresolvable refs or network error), fall through to plain load
179
+ console.warn(`[kubb] Failed to bundle external $refs in "${pathOrApi}": ${(e as Error).message}. Falling back to plain load.`)
180
+ }
174
181
  }
175
182
 
176
183
  const oasNormalize = new OASNormalize(pathOrApi, {
@@ -191,7 +198,7 @@ export async function parse(
191
198
  }
192
199
 
193
200
  export async function merge(pathOrApi: Array<string | Document>, { oasClass = Oas }: { oasClass?: typeof Oas } = {}): Promise<Oas> {
194
- const instances = await Promise.all(pathOrApi.map((p) => parse(p, { oasClass, enablePaths: false, canBundle: false })))
201
+ const instances = await Promise.all(pathOrApi.map((p) => parse(p, { oasClass, enablePaths: false })))
195
202
 
196
203
  if (instances.length === 0) {
197
204
  throw new Error('No OAS instances provided for merging.')