@redocly/cli 1.9.0 → 1.9.1
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/CHANGELOG.md +9 -0
- package/lib/__mocks__/@redocly/openapi-core.js +2 -1
- package/lib/__mocks__/documents.d.ts +58 -0
- package/lib/__mocks__/documents.js +63 -3
- package/lib/__tests__/commands/join.test.js +126 -3
- package/lib/commands/join.d.ts +2 -1
- package/lib/commands/join.js +20 -26
- package/lib/commands/split/index.d.ts +3 -1
- package/lib/commands/split/index.js +10 -9
- package/lib/commands/split/types.d.ts +1 -2
- package/lib/commands/split/types.js +1 -2
- package/lib/utils/js-utils.d.ts +1 -1
- package/package.json +2 -2
- package/src/__mocks__/@redocly/openapi-core.ts +4 -1
- package/src/__mocks__/documents.ts +63 -2
- package/src/__tests__/commands/join.test.ts +137 -3
- package/src/commands/join.ts +25 -31
- package/src/commands/split/index.ts +24 -25
- package/src/commands/split/types.ts +1 -3
- package/src/utils/js-utils.ts +1 -1
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { handleJoin } from '../../commands/join';
|
|
2
|
-
import { exitWithError, writeToFileByExtension, writeYaml } from '../../utils/miscellaneous';
|
|
3
1
|
import { yellow } from 'colorette';
|
|
4
2
|
import { detectSpec } from '@redocly/openapi-core';
|
|
3
|
+
import { handleJoin } from '../../commands/join';
|
|
4
|
+
import { exitWithError, writeToFileByExtension } from '../../utils/miscellaneous';
|
|
5
5
|
import { loadConfig } from '../../__mocks__/@redocly/openapi-core';
|
|
6
6
|
import { ConfigFixture } from '../fixtures/config';
|
|
7
7
|
|
|
@@ -9,7 +9,7 @@ jest.mock('../../utils/miscellaneous');
|
|
|
9
9
|
|
|
10
10
|
jest.mock('colorette');
|
|
11
11
|
|
|
12
|
-
describe('handleJoin
|
|
12
|
+
describe('handleJoin', () => {
|
|
13
13
|
const colloreteYellowMock = yellow as jest.Mock<any, any>;
|
|
14
14
|
colloreteYellowMock.mockImplementation((string: string) => string);
|
|
15
15
|
|
|
@@ -181,4 +181,138 @@ describe('handleJoin fails', () => {
|
|
|
181
181
|
expect(config.styleguide.skipDecorators).not.toHaveBeenCalled();
|
|
182
182
|
expect(config.styleguide.skipPreprocessors).not.toHaveBeenCalled();
|
|
183
183
|
});
|
|
184
|
+
|
|
185
|
+
it('should handle join with prefix-components-with-info-prop and null values', async () => {
|
|
186
|
+
(detectSpec as jest.Mock).mockReturnValue('oas3_0');
|
|
187
|
+
|
|
188
|
+
await handleJoin(
|
|
189
|
+
{
|
|
190
|
+
apis: ['first.yaml', 'second.yaml', 'third.yaml'],
|
|
191
|
+
'prefix-components-with-info-prop': 'title',
|
|
192
|
+
output: 'join-result.yaml',
|
|
193
|
+
},
|
|
194
|
+
ConfigFixture as any,
|
|
195
|
+
'cli-version'
|
|
196
|
+
);
|
|
197
|
+
|
|
198
|
+
expect(writeToFileByExtension).toHaveBeenCalledWith(
|
|
199
|
+
{
|
|
200
|
+
openapi: '3.0.0',
|
|
201
|
+
info: {
|
|
202
|
+
description: 'example test',
|
|
203
|
+
version: '1.0.0',
|
|
204
|
+
title: 'First API',
|
|
205
|
+
termsOfService: 'http://swagger.io/terms/',
|
|
206
|
+
license: {
|
|
207
|
+
name: 'Apache 2.0',
|
|
208
|
+
url: 'http://www.apache.org/licenses/LICENSE-2.0.html',
|
|
209
|
+
},
|
|
210
|
+
},
|
|
211
|
+
servers: [
|
|
212
|
+
{
|
|
213
|
+
url: 'http://localhost:8080',
|
|
214
|
+
},
|
|
215
|
+
{
|
|
216
|
+
url: 'https://api.server.test/v1',
|
|
217
|
+
},
|
|
218
|
+
],
|
|
219
|
+
tags: [
|
|
220
|
+
{
|
|
221
|
+
name: 'pet',
|
|
222
|
+
'x-displayName': 'pet',
|
|
223
|
+
},
|
|
224
|
+
],
|
|
225
|
+
paths: {
|
|
226
|
+
'/GETUser/{userId}': {
|
|
227
|
+
summary: 'get user by id',
|
|
228
|
+
description: 'user info',
|
|
229
|
+
servers: [
|
|
230
|
+
{
|
|
231
|
+
url: '/user',
|
|
232
|
+
},
|
|
233
|
+
{
|
|
234
|
+
url: '/pet',
|
|
235
|
+
description: 'pet server',
|
|
236
|
+
},
|
|
237
|
+
],
|
|
238
|
+
get: {
|
|
239
|
+
tags: ['pet'],
|
|
240
|
+
summary: 'Find pet by ID',
|
|
241
|
+
description: 'Returns a single pet',
|
|
242
|
+
operationId: 'getPetById',
|
|
243
|
+
servers: [
|
|
244
|
+
{
|
|
245
|
+
url: '/pet',
|
|
246
|
+
},
|
|
247
|
+
],
|
|
248
|
+
},
|
|
249
|
+
parameters: [
|
|
250
|
+
{
|
|
251
|
+
name: 'param1',
|
|
252
|
+
in: 'header',
|
|
253
|
+
schema: {
|
|
254
|
+
description: 'string',
|
|
255
|
+
},
|
|
256
|
+
},
|
|
257
|
+
],
|
|
258
|
+
},
|
|
259
|
+
},
|
|
260
|
+
components: {
|
|
261
|
+
schemas: {
|
|
262
|
+
'Third API_SchemaWithNull': {
|
|
263
|
+
type: 'string',
|
|
264
|
+
default: null,
|
|
265
|
+
nullable: true,
|
|
266
|
+
},
|
|
267
|
+
'Third API_SchemaWithRef': {
|
|
268
|
+
type: 'object',
|
|
269
|
+
properties: {
|
|
270
|
+
schemaType: {
|
|
271
|
+
type: 'string',
|
|
272
|
+
enum: ['foo'],
|
|
273
|
+
},
|
|
274
|
+
foo: {
|
|
275
|
+
$ref: '#/components/schemas/Third API_SchemaWithNull',
|
|
276
|
+
},
|
|
277
|
+
},
|
|
278
|
+
},
|
|
279
|
+
'Third API_SchemaWithDiscriminator': {
|
|
280
|
+
discriminator: {
|
|
281
|
+
propertyName: 'schemaType',
|
|
282
|
+
mapping: {
|
|
283
|
+
foo: '#/components/schemas/Third API_SchemaWithRef',
|
|
284
|
+
bar: '#/components/schemas/Third API_SchemaWithNull',
|
|
285
|
+
},
|
|
286
|
+
},
|
|
287
|
+
oneOf: [
|
|
288
|
+
{
|
|
289
|
+
$ref: '#/components/schemas/Third API_SchemaWithRef',
|
|
290
|
+
},
|
|
291
|
+
{
|
|
292
|
+
type: 'object',
|
|
293
|
+
properties: {
|
|
294
|
+
schemaType: {
|
|
295
|
+
type: 'string',
|
|
296
|
+
enum: ['bar'],
|
|
297
|
+
},
|
|
298
|
+
bar: {
|
|
299
|
+
type: 'string',
|
|
300
|
+
},
|
|
301
|
+
},
|
|
302
|
+
},
|
|
303
|
+
],
|
|
304
|
+
},
|
|
305
|
+
},
|
|
306
|
+
},
|
|
307
|
+
'x-tagGroups': [
|
|
308
|
+
{
|
|
309
|
+
name: 'First API',
|
|
310
|
+
tags: ['pet'],
|
|
311
|
+
},
|
|
312
|
+
],
|
|
313
|
+
},
|
|
314
|
+
'join-result.yaml',
|
|
315
|
+
true
|
|
316
|
+
);
|
|
317
|
+
});
|
|
184
318
|
});
|
package/src/commands/join.ts
CHANGED
|
@@ -4,22 +4,16 @@ import { performance } from 'perf_hooks';
|
|
|
4
4
|
const isEqual = require('lodash.isequal');
|
|
5
5
|
import {
|
|
6
6
|
Config,
|
|
7
|
-
Oas3Definition,
|
|
8
7
|
SpecVersion,
|
|
9
8
|
BaseResolver,
|
|
10
|
-
Document,
|
|
11
9
|
StyleguideConfig,
|
|
12
|
-
Oas3Tag,
|
|
13
10
|
formatProblems,
|
|
14
11
|
getTotals,
|
|
15
12
|
lintDocument,
|
|
16
13
|
detectSpec,
|
|
17
14
|
bundleDocument,
|
|
18
|
-
Referenced,
|
|
19
15
|
isRef,
|
|
20
|
-
RuleSeverity,
|
|
21
16
|
} from '@redocly/openapi-core';
|
|
22
|
-
|
|
23
17
|
import {
|
|
24
18
|
getFallbackApisOrExit,
|
|
25
19
|
printExecutionTime,
|
|
@@ -32,16 +26,24 @@ import {
|
|
|
32
26
|
checkForDeprecatedOptions,
|
|
33
27
|
} from '../utils/miscellaneous';
|
|
34
28
|
import { isObject, isString, keysOf } from '../utils/js-utils';
|
|
35
|
-
import {
|
|
29
|
+
import { COMPONENTS, OPENAPI3_METHOD } from './split/types';
|
|
30
|
+
import { crawl, startsWithComponents } from './split';
|
|
31
|
+
|
|
32
|
+
import type {
|
|
33
|
+
Oas3Definition,
|
|
34
|
+
Document,
|
|
35
|
+
Oas3Tag,
|
|
36
|
+
Referenced,
|
|
37
|
+
RuleSeverity,
|
|
38
|
+
} from '@redocly/openapi-core';
|
|
39
|
+
import type { BundleResult } from '@redocly/openapi-core/lib/bundle';
|
|
40
|
+
import type {
|
|
36
41
|
Oas3Parameter,
|
|
37
42
|
Oas3PathItem,
|
|
38
43
|
Oas3Server,
|
|
39
44
|
Oas3_1Definition,
|
|
40
45
|
} from '@redocly/openapi-core/lib/typings/openapi';
|
|
41
|
-
import { OPENAPI3_METHOD } from './split/types';
|
|
42
|
-
import { BundleResult } from '@redocly/openapi-core/lib/bundle';
|
|
43
46
|
|
|
44
|
-
const COMPONENTS = 'components';
|
|
45
47
|
const Tags = 'tags';
|
|
46
48
|
const xTagGroups = 'x-tagGroups';
|
|
47
49
|
let potentialConflictsTotal = 0;
|
|
@@ -756,8 +758,11 @@ function addComponentsPrefix(description: string, componentsPrefix: string) {
|
|
|
756
758
|
function addSecurityPrefix(security: any, componentsPrefix: string) {
|
|
757
759
|
return componentsPrefix
|
|
758
760
|
? security?.map((s: any) => {
|
|
759
|
-
const
|
|
760
|
-
|
|
761
|
+
const joinedSecuritySchema = {};
|
|
762
|
+
for (const [key, value] of Object.entries(s)) {
|
|
763
|
+
Object.assign(joinedSecuritySchema, { [componentsPrefix + '_' + key]: value });
|
|
764
|
+
}
|
|
765
|
+
return joinedSecuritySchema;
|
|
761
766
|
})
|
|
762
767
|
: security;
|
|
763
768
|
}
|
|
@@ -798,30 +803,19 @@ async function validateApi(
|
|
|
798
803
|
}
|
|
799
804
|
}
|
|
800
805
|
|
|
801
|
-
function
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
visitor(object[key], key);
|
|
805
|
-
crawl(object[key], visitor);
|
|
806
|
-
}
|
|
807
|
-
}
|
|
808
|
-
|
|
809
|
-
function replace$Refs(obj: any, componentsPrefix: string) {
|
|
810
|
-
crawl(obj, (node: any) => {
|
|
811
|
-
if (node.$ref && isString(node.$ref) && node.$ref.startsWith(`#/${COMPONENTS}/`)) {
|
|
806
|
+
function replace$Refs(obj: unknown, componentsPrefix: string) {
|
|
807
|
+
crawl(obj, (node: Record<string, unknown>) => {
|
|
808
|
+
if (node.$ref && typeof node.$ref === 'string' && startsWithComponents(node.$ref)) {
|
|
812
809
|
const name = path.basename(node.$ref);
|
|
813
810
|
node.$ref = node.$ref.replace(name, componentsPrefix + '_' + name);
|
|
814
|
-
} else if (
|
|
815
|
-
node.discriminator &&
|
|
816
|
-
node.discriminator.mapping &&
|
|
817
|
-
isObject(node.discriminator.mapping)
|
|
818
|
-
) {
|
|
811
|
+
} else if (isObject(node.discriminator) && isObject(node.discriminator.mapping)) {
|
|
819
812
|
const { mapping } = node.discriminator;
|
|
820
813
|
for (const name of Object.keys(mapping)) {
|
|
821
|
-
|
|
822
|
-
|
|
814
|
+
const mappingPointer = mapping[name];
|
|
815
|
+
if (typeof mappingPointer === 'string' && startsWithComponents(mappingPointer)) {
|
|
816
|
+
mapping[name] = mappingPointer
|
|
823
817
|
.split('/')
|
|
824
|
-
.map((name
|
|
818
|
+
.map((name, i, arr) => {
|
|
825
819
|
return arr.length - 1 === i && !name.includes(componentsPrefix)
|
|
826
820
|
? componentsPrefix + '_' + name
|
|
827
821
|
: name;
|
|
@@ -1,11 +1,9 @@
|
|
|
1
1
|
import { red, blue, yellow, green } from 'colorette';
|
|
2
2
|
import * as fs from 'fs';
|
|
3
3
|
import { parseYaml, slash, isRef, isTruthy } from '@redocly/openapi-core';
|
|
4
|
-
import type { OasRef } from '@redocly/openapi-core';
|
|
5
4
|
import * as path from 'path';
|
|
6
5
|
import { performance } from 'perf_hooks';
|
|
7
6
|
const isEqual = require('lodash.isequal');
|
|
8
|
-
|
|
9
7
|
import {
|
|
10
8
|
printExecutionTime,
|
|
11
9
|
pathToFilename,
|
|
@@ -16,8 +14,16 @@ import {
|
|
|
16
14
|
writeToFileByExtension,
|
|
17
15
|
getAndValidateFileExtension,
|
|
18
16
|
} from '../../utils/miscellaneous';
|
|
19
|
-
import {
|
|
17
|
+
import { isObject, isEmptyObject } from '../../utils/js-utils';
|
|
20
18
|
import {
|
|
19
|
+
OPENAPI3_COMPONENT,
|
|
20
|
+
COMPONENTS,
|
|
21
|
+
OPENAPI3_METHOD_NAMES,
|
|
22
|
+
OPENAPI3_COMPONENT_NAMES,
|
|
23
|
+
} from './types';
|
|
24
|
+
|
|
25
|
+
import type { OasRef } from '@redocly/openapi-core';
|
|
26
|
+
import type {
|
|
21
27
|
Definition,
|
|
22
28
|
Oas2Definition,
|
|
23
29
|
Oas3Schema,
|
|
@@ -26,13 +32,8 @@ import {
|
|
|
26
32
|
Oas3Components,
|
|
27
33
|
Oas3ComponentName,
|
|
28
34
|
ComponentsFiles,
|
|
29
|
-
|
|
35
|
+
RefObject,
|
|
30
36
|
Oas3PathItem,
|
|
31
|
-
OPENAPI3_COMPONENT,
|
|
32
|
-
COMPONENTS,
|
|
33
|
-
componentsPath,
|
|
34
|
-
OPENAPI3_METHOD_NAMES,
|
|
35
|
-
OPENAPI3_COMPONENT_NAMES,
|
|
36
37
|
Referenced,
|
|
37
38
|
} from './types';
|
|
38
39
|
|
|
@@ -93,8 +94,8 @@ function splitDefinition(
|
|
|
93
94
|
writeToFileByExtension(openapi, path.join(openapiDir, `openapi.${ext}`));
|
|
94
95
|
}
|
|
95
96
|
|
|
96
|
-
function
|
|
97
|
-
return node.startsWith(
|
|
97
|
+
export function startsWithComponents(node: string) {
|
|
98
|
+
return node.startsWith(`#/${COMPONENTS}/`);
|
|
98
99
|
}
|
|
99
100
|
|
|
100
101
|
function isSupportedExtension(filename: string) {
|
|
@@ -144,33 +145,31 @@ function traverseDirectoryDeepCallback(
|
|
|
144
145
|
writeToFileByExtension(pathData, filename);
|
|
145
146
|
}
|
|
146
147
|
|
|
147
|
-
function crawl(object:
|
|
148
|
+
export function crawl(object: unknown, visitor: (node: Record<string, unknown>) => void) {
|
|
148
149
|
if (!isObject(object)) return;
|
|
150
|
+
|
|
151
|
+
visitor(object);
|
|
149
152
|
for (const key of Object.keys(object)) {
|
|
150
|
-
visitor(object, key);
|
|
151
153
|
crawl(object[key], visitor);
|
|
152
154
|
}
|
|
153
155
|
}
|
|
154
156
|
|
|
155
|
-
function replace$Refs(obj:
|
|
156
|
-
crawl(obj, (node:
|
|
157
|
-
if (node.$ref &&
|
|
158
|
-
replace(node, '$ref');
|
|
159
|
-
} else if (
|
|
160
|
-
node.discriminator &&
|
|
161
|
-
node.discriminator.mapping &&
|
|
162
|
-
isObject(node.discriminator.mapping)
|
|
163
|
-
) {
|
|
157
|
+
function replace$Refs(obj: unknown, relativeFrom: string, componentFiles = {} as ComponentsFiles) {
|
|
158
|
+
crawl(obj, (node: Record<string, unknown>) => {
|
|
159
|
+
if (node.$ref && typeof node.$ref === 'string' && startsWithComponents(node.$ref)) {
|
|
160
|
+
replace(node as RefObject, '$ref');
|
|
161
|
+
} else if (isObject(node.discriminator) && isObject(node.discriminator.mapping)) {
|
|
164
162
|
const { mapping } = node.discriminator;
|
|
165
163
|
for (const name of Object.keys(mapping)) {
|
|
166
|
-
|
|
167
|
-
|
|
164
|
+
const mappingPointer = mapping[name];
|
|
165
|
+
if (typeof mappingPointer === 'string' && startsWithComponents(mappingPointer)) {
|
|
166
|
+
replace(node.discriminator.mapping as RefObject, name);
|
|
168
167
|
}
|
|
169
168
|
}
|
|
170
169
|
}
|
|
171
170
|
});
|
|
172
171
|
|
|
173
|
-
function replace(node:
|
|
172
|
+
function replace(node: RefObject, key: string) {
|
|
174
173
|
const splittedNode = node[key].split('/');
|
|
175
174
|
const name = splittedNode.pop();
|
|
176
175
|
const groupName = splittedNode[2];
|
|
@@ -28,7 +28,7 @@ export type Definition = Oas3_1Definition | Oas3Definition | Oas2Definition;
|
|
|
28
28
|
export interface ComponentsFiles {
|
|
29
29
|
[schemas: string]: any;
|
|
30
30
|
}
|
|
31
|
-
export interface
|
|
31
|
+
export interface RefObject {
|
|
32
32
|
[$ref: string]: string;
|
|
33
33
|
}
|
|
34
34
|
|
|
@@ -36,8 +36,6 @@ export const COMPONENTS = 'components';
|
|
|
36
36
|
export const PATHS = 'paths';
|
|
37
37
|
export const WEBHOOKS = 'webhooks';
|
|
38
38
|
export const xWEBHOOKS = 'x-webhooks';
|
|
39
|
-
export const componentsPath = `#/${COMPONENTS}/`;
|
|
40
|
-
|
|
41
39
|
export enum OPENAPI3_METHOD {
|
|
42
40
|
get = 'get',
|
|
43
41
|
put = 'put',
|
package/src/utils/js-utils.ts
CHANGED