diff --git a/docs/commands/bundle.md b/docs/commands/bundle.md index 88ac61c75c..bff68c5602 100644 --- a/docs/commands/bundle.md +++ b/docs/commands/bundle.md @@ -29,6 +29,7 @@ Option | Type | Description entrypoints | [string] | List of API root definition filenames or names assigned in the `apis` section of your Redocly configuration file. Default values are all names defined in the `apis` section within your configuration file. --config | string | Specify path to the [config file](#custom-configuration-file). --dereferenced, -d | boolean | Generate fully dereferenced bundle. +--keep-url-references, -k | boolean | Keep absolute url references. --ext | string | Specify bundled file extension. Possible values are `json`, `yaml`, or `yml`. Default value is `yaml`. --extends | [string] | Can be used in combination with `--lint` to [extend a specific configuration](./lint.md#extend-configuration). Default values are taken from the Redocly configuration file. --force, -f | boolean | Generate bundle output even when errors occur. diff --git a/packages/cli/src/commands/bundle.ts b/packages/cli/src/commands/bundle.ts index 4403b845fd..3911fbb75d 100644 --- a/packages/cli/src/commands/bundle.ts +++ b/packages/cli/src/commands/bundle.ts @@ -39,6 +39,7 @@ export async function handleBundle( metafile?: string; extends?: string[]; 'remove-unused-components'?: boolean; + 'keep-url-references'?: boolean; }, version: string, ) { @@ -98,6 +99,7 @@ export async function handleBundle( ref: path, dereference: argv.dereferenced, removeUnusedComponents, + keepUrlRefs: argv['keep-url-references'], }); const fileTotals = getTotals(problems); diff --git a/packages/core/__tests__/__snapshots__/bundle.test.ts.snap b/packages/core/__tests__/__snapshots__/bundle.test.ts.snap index a4069fd63f..8b867cf007 100644 --- a/packages/core/__tests__/__snapshots__/bundle.test.ts.snap +++ b/packages/core/__tests__/__snapshots__/bundle.test.ts.snap @@ -100,6 +100,32 @@ components: `; +exports[`bundle should not bundle url refs if used with keepUrlRefs 1`] = ` +openapi: 3.0.0 +paths: + /pet: + parameters: + - $ref: '#/components/parameters/path-param' + put: + parameters: + - $ref: https://someexternal.schema + - $ref: '#/components/parameters/param-b' + - name: test + get: + parameters: + - $ref: http://someexternal.schema + - $ref: '#/components/parameters/param-c' +components: + parameters: + path-param: + name: path_param + param-c: + name: param_c + param-b: + name: param_b + +`; + exports[`bundle should not place referened schema inline when component in question is not of type "schemas" 1`] = ` openapi: 3.0.0 paths: diff --git a/packages/core/__tests__/bundle.test.ts b/packages/core/__tests__/bundle.test.ts index 8d38dda828..76bc8d950e 100644 --- a/packages/core/__tests__/bundle.test.ts +++ b/packages/core/__tests__/bundle.test.ts @@ -128,5 +128,30 @@ describe('bundle', () => { } ); expect(res.parsed).toMatchSnapshot(); - }) + }); + + it('should not bundle url refs if used with keepUrlRefs', async () => { + const fetchMock = jest.fn(() => + Promise.resolve({ + ok: true, + text: () => 'External schema content', + headers: { + get: () => '', + }, + }), + ); + const { bundle: res, problems } = await bundle({ + config: new Config({} as ResolvedConfig), + externalRefResolver: new BaseResolver({ + http: { + customFetch: fetchMock, + headers: [], + }, + }), + ref: path.join(__dirname, 'fixtures/refs/openapi-with-url-refs.yaml'), + keepUrlRefs: true, + }); + expect(problems).toHaveLength(0); + expect(res.parsed).toMatchSnapshot(); + }); }); diff --git a/packages/core/__tests__/fixtures/refs/openapi-with-url-refs.yaml b/packages/core/__tests__/fixtures/refs/openapi-with-url-refs.yaml new file mode 100644 index 0000000000..7f2681512b --- /dev/null +++ b/packages/core/__tests__/fixtures/refs/openapi-with-url-refs.yaml @@ -0,0 +1,18 @@ +openapi: 3.0.0 +paths: + /pet: + parameters: + - $ref: '#/components/parameters/path-param' + put: + parameters: + - $ref: 'https://someexternal.schema' + - $ref: ./param-b.yaml + - name: test + get: + parameters: + - $ref: 'http://someexternal.schema' + - $ref: ./param-c.yaml +components: + parameters: + path-param: + name: path_param diff --git a/packages/core/src/bundle.ts b/packages/core/src/bundle.ts index a5615389fc..096230904a 100755 --- a/packages/core/src/bundle.ts +++ b/packages/core/src/bundle.ts @@ -7,7 +7,7 @@ import { Oas3_1Types } from './types/oas3_1'; import { NormalizedNodeType, normalizeTypes, NodeType } from './types'; import { WalkContext, walkDocument, UserContext, ResolveResult } from './walk'; import { detectOpenAPI, openAPIMajor, OasMajorVersion } from './oas-types'; -import { isRef, Location, refBaseName } from './ref-utils'; +import {isAbsoluteUrl, isRef, Location, refBaseName} from './ref-utils'; import { initRules } from './config/rules'; import { reportUnresolvedRef } from './rules/no-unresolved-refs'; import { isPlainObject } from './utils'; @@ -35,6 +35,7 @@ export async function bundle(opts: { base?: string; skipRedoclyRegistryRefs?: boolean; removeUnusedComponents?: boolean; + keepUrlRefs?: boolean; }) { const { ref, @@ -71,6 +72,7 @@ export async function bundleDocument(opts: { dereference?: boolean; skipRedoclyRegistryRefs?: boolean; removeUnusedComponents?: boolean; + keepUrlRefs?: boolean; }) { const { document, @@ -80,6 +82,7 @@ export async function bundleDocument(opts: { dereference = false, skipRedoclyRegistryRefs = false, removeUnusedComponents = false, + keepUrlRefs = false, } = opts; const oasVersion = detectOpenAPI(document.parsed); const oasMajorVersion = openAPIMajor(oasVersion); @@ -128,7 +131,14 @@ export async function bundleDocument(opts: { { severity: 'error', ruleId: 'bundler', - visitor: makeBundleVisitor(oasMajorVersion, dereference, skipRedoclyRegistryRefs, document, resolvedRefMap), + visitor: makeBundleVisitor( + oasMajorVersion, + dereference, + skipRedoclyRegistryRefs, + document, + resolvedRefMap, + keepUrlRefs, + ), }, ...decorators, ] as any, @@ -199,7 +209,8 @@ function makeBundleVisitor( dereference: boolean, skipRedoclyRegistryRefs: boolean, rootDocument: Document, - resolvedRefMap: ResolvedRefMap + resolvedRefMap: ResolvedRefMap, + keepUrlRefs: boolean, ) { let components: Record>; @@ -224,6 +235,10 @@ function makeBundleVisitor( return; } + if (keepUrlRefs && isAbsoluteUrl(node.$ref)) { + return; + } + const componentType = mapTypeToComponent(ctx.type.name, version); if (!componentType) { replaceRef(node, resolved, ctx); diff --git a/packages/core/src/ref-utils.ts b/packages/core/src/ref-utils.ts index 663baeab8c..6b20b1a06e 100644 --- a/packages/core/src/ref-utils.ts +++ b/packages/core/src/ref-utils.ts @@ -72,6 +72,7 @@ export function isMappingRef(mapping: string) { return ( mapping.startsWith('#') || mapping.startsWith('https://') || + mapping.startsWith('http://') || mapping.startsWith('./') || mapping.startsWith('../') || mapping.indexOf('/') > -1