@redocly/cli 1.0.0-beta.100 → 1.0.0-beta.103
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/lib/__mocks__/@redocly/openapi-core.d.ts +4 -0
- package/lib/__mocks__/@redocly/openapi-core.js +4 -5
- package/lib/__mocks__/perf_hooks.d.ts +4 -0
- package/lib/__mocks__/perf_hooks.js +6 -0
- package/lib/__mocks__/utils.d.ts +1 -3
- package/lib/__mocks__/utils.js +2 -2
- package/lib/__tests__/commands/join.test.d.ts +1 -0
- package/lib/__tests__/commands/join.test.js +41 -0
- package/lib/__tests__/commands/lint.test.d.ts +1 -0
- package/lib/__tests__/commands/lint.test.js +106 -0
- package/lib/__tests__/commands/push.test.js +30 -1
- package/lib/__tests__/fixtures/config.d.ts +12 -0
- package/lib/__tests__/fixtures/config.js +14 -0
- package/lib/__tests__/utils.test.js +39 -1
- package/lib/commands/join.d.ts +5 -2
- package/lib/commands/join.js +78 -40
- package/lib/commands/lint.d.ts +5 -3
- package/lib/commands/lint.js +24 -1
- package/lib/commands/preview-docs/preview-server/oauth2-redirect.html +1 -1
- package/lib/commands/preview-docs/preview-server/preview-server.js +3 -3
- package/lib/commands/push.d.ts +2 -1
- package/lib/commands/push.js +11 -1
- package/lib/index.js +27 -3
- package/lib/utils.d.ts +1 -0
- package/lib/utils.js +14 -2
- package/package.json +2 -2
- package/src/__mocks__/@redocly/openapi-core.ts +4 -4
- package/src/__mocks__/perf_hooks.ts +3 -0
- package/src/__mocks__/utils.ts +1 -1
- package/src/__tests__/commands/join.test.ts +47 -0
- package/src/__tests__/commands/lint.test.ts +133 -0
- package/src/__tests__/commands/push.test.ts +34 -1
- package/src/__tests__/fixtures/config.ts +11 -0
- package/src/__tests__/utils.test.ts +52 -2
- package/src/commands/join.ts +154 -64
- package/src/commands/lint.ts +58 -17
- package/src/commands/preview-docs/preview-server/oauth2-redirect.html +1 -1
- package/src/commands/preview-docs/preview-server/preview-server.ts +3 -3
- package/src/commands/push.ts +20 -2
- package/src/index.ts +38 -13
- package/src/utils.ts +19 -1
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -24,9 +24,12 @@ export declare const RedoclyClient: jest.Mock<{
|
|
|
24
24
|
export declare const loadConfig: jest.Mock<{
|
|
25
25
|
configFile: null;
|
|
26
26
|
lint: {
|
|
27
|
+
addIgnore: jest.Mock<any, any>;
|
|
27
28
|
skipRules: jest.Mock<any, any>;
|
|
28
29
|
skipPreprocessors: jest.Mock<any, any>;
|
|
30
|
+
saveIgnore: jest.Mock<any, any>;
|
|
29
31
|
skipDecorators: jest.Mock<any, any>;
|
|
32
|
+
ignore: null;
|
|
30
33
|
};
|
|
31
34
|
}, []>;
|
|
32
35
|
export declare const getMergedConfig: jest.Mock<any, any>;
|
|
@@ -42,3 +45,4 @@ export declare const getTotals: jest.Mock<{
|
|
|
42
45
|
}, []>;
|
|
43
46
|
export declare const formatProblems: jest.Mock<any, any>;
|
|
44
47
|
export declare const slash: jest.Mock<any, any>;
|
|
48
|
+
export declare const findConfig: jest.Mock<any, any>;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.slash = exports.formatProblems = exports.getTotals = exports.bundle = exports.lint = exports.getMergedConfig = exports.loadConfig = exports.RedoclyClient = exports.__redoclyClient = void 0;
|
|
3
|
+
exports.findConfig = exports.slash = exports.formatProblems = exports.getTotals = exports.bundle = exports.lint = exports.getMergedConfig = exports.loadConfig = exports.RedoclyClient = exports.__redoclyClient = void 0;
|
|
4
|
+
const config_1 = require("./../../__tests__/fixtures/config");
|
|
4
5
|
exports.__redoclyClient = {
|
|
5
6
|
isAuthorizedWithRedocly: jest.fn().mockResolvedValue(true),
|
|
6
7
|
isAuthorizedWithRedoclyByRegion: jest.fn().mockResolvedValue(true),
|
|
@@ -16,13 +17,11 @@ exports.__redoclyClient = {
|
|
|
16
17
|
},
|
|
17
18
|
};
|
|
18
19
|
exports.RedoclyClient = jest.fn(() => exports.__redoclyClient);
|
|
19
|
-
exports.loadConfig = jest.fn(() =>
|
|
20
|
-
configFile: null,
|
|
21
|
-
lint: { skipRules: jest.fn(), skipPreprocessors: jest.fn(), skipDecorators: jest.fn() },
|
|
22
|
-
}));
|
|
20
|
+
exports.loadConfig = jest.fn(() => config_1.ConfigFixture);
|
|
23
21
|
exports.getMergedConfig = jest.fn();
|
|
24
22
|
exports.lint = jest.fn();
|
|
25
23
|
exports.bundle = jest.fn(() => ({ bundle: { parsed: null }, problems: null }));
|
|
26
24
|
exports.getTotals = jest.fn(() => ({ errors: 0 }));
|
|
27
25
|
exports.formatProblems = jest.fn();
|
|
28
26
|
exports.slash = jest.fn();
|
|
27
|
+
exports.findConfig = jest.fn();
|
package/lib/__mocks__/utils.d.ts
CHANGED
|
@@ -1,8 +1,5 @@
|
|
|
1
1
|
/// <reference types="jest" />
|
|
2
2
|
export declare const getFallbackEntryPointsOrExit: jest.Mock<any, [entrypoints: any]>;
|
|
3
|
-
export declare const getTotals: jest.Mock<{
|
|
4
|
-
errors: number;
|
|
5
|
-
}, []>;
|
|
6
3
|
export declare const dumpBundle: jest.Mock<string, []>;
|
|
7
4
|
export declare const slash: jest.Mock<any, any>;
|
|
8
5
|
export declare const pluralize: jest.Mock<any, any>;
|
|
@@ -15,3 +12,4 @@ export declare const getOutputFileName: jest.Mock<{
|
|
|
15
12
|
ext: string;
|
|
16
13
|
}, []>;
|
|
17
14
|
export declare const handleError: jest.Mock<any, any>;
|
|
15
|
+
export declare const exitWithError: jest.Mock<any, any>;
|
package/lib/__mocks__/utils.js
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.handleError = exports.getOutputFileName = exports.printLintTotals = exports.printUnusedWarnings = exports.printExecutionTime = exports.getExecutionTime = exports.pluralize = exports.slash = exports.dumpBundle = exports.
|
|
3
|
+
exports.exitWithError = exports.handleError = exports.getOutputFileName = exports.printLintTotals = exports.printUnusedWarnings = exports.printExecutionTime = exports.getExecutionTime = exports.pluralize = exports.slash = exports.dumpBundle = exports.getFallbackEntryPointsOrExit = void 0;
|
|
4
4
|
exports.getFallbackEntryPointsOrExit = jest.fn((entrypoints) => entrypoints.map(() => ({ path: '' })));
|
|
5
|
-
exports.getTotals = jest.fn(() => ({ errors: 0 }));
|
|
6
5
|
exports.dumpBundle = jest.fn(() => '');
|
|
7
6
|
exports.slash = jest.fn();
|
|
8
7
|
exports.pluralize = jest.fn();
|
|
@@ -12,3 +11,4 @@ exports.printUnusedWarnings = jest.fn();
|
|
|
12
11
|
exports.printLintTotals = jest.fn();
|
|
13
12
|
exports.getOutputFileName = jest.fn(() => ({ outputFile: 'test.yaml', ext: 'yaml' }));
|
|
14
13
|
exports.handleError = jest.fn();
|
|
14
|
+
exports.exitWithError = jest.fn();
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
const join_1 = require("../../commands/join");
|
|
13
|
+
const utils_1 = require("../../utils");
|
|
14
|
+
const colorette_1 = require("colorette");
|
|
15
|
+
jest.mock('../../utils');
|
|
16
|
+
jest.mock('colorette');
|
|
17
|
+
describe('handleJoin fails', () => {
|
|
18
|
+
const colloreteYellowMock = colorette_1.yellow;
|
|
19
|
+
colloreteYellowMock.mockImplementation((string) => string);
|
|
20
|
+
it('should call exitWithError because only one entrypoint', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
21
|
+
yield join_1.handleJoin({ entrypoints: ['first.yaml'] }, 'cli-version');
|
|
22
|
+
expect(utils_1.exitWithError).toHaveBeenCalledWith(`At least 2 entrypoints should be provided. \n\n`);
|
|
23
|
+
}));
|
|
24
|
+
it('should call exitWithError because passed all 3 options for tags', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
25
|
+
yield join_1.handleJoin({
|
|
26
|
+
entrypoints: ['first.yaml', 'second.yaml'],
|
|
27
|
+
'prefix-tags-with-info-prop': 'something',
|
|
28
|
+
'without-x-tag-groups': true,
|
|
29
|
+
'prefix-tags-with-filename': true,
|
|
30
|
+
}, 'cli-version');
|
|
31
|
+
expect(utils_1.exitWithError).toHaveBeenCalledWith(`You use prefix-tags-with-filename, prefix-tags-with-info-prop, without-x-tag-groups together.\nPlease choose only one! \n\n`);
|
|
32
|
+
}));
|
|
33
|
+
it('should call exitWithError because passed all 2 options for tags', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
34
|
+
yield join_1.handleJoin({
|
|
35
|
+
entrypoints: ['first.yaml', 'second.yaml'],
|
|
36
|
+
'without-x-tag-groups': true,
|
|
37
|
+
'prefix-tags-with-filename': true,
|
|
38
|
+
}, 'cli-version');
|
|
39
|
+
expect(utils_1.exitWithError).toHaveBeenCalledWith(`You use prefix-tags-with-filename, without-x-tag-groups together.\nPlease choose only one! \n\n`);
|
|
40
|
+
}));
|
|
41
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
const lint_1 = require("../../commands/lint");
|
|
13
|
+
const openapi_core_1 = require("@redocly/openapi-core");
|
|
14
|
+
const utils_1 = require("../../utils");
|
|
15
|
+
const config_1 = require("../fixtures/config");
|
|
16
|
+
const perf_hooks_1 = require("perf_hooks");
|
|
17
|
+
jest.mock('@redocly/openapi-core');
|
|
18
|
+
jest.mock('../../utils');
|
|
19
|
+
jest.mock('perf_hooks');
|
|
20
|
+
const argvMock = {
|
|
21
|
+
entrypoints: ['openapi.yaml'],
|
|
22
|
+
'lint-config': 'off',
|
|
23
|
+
format: 'codeframe',
|
|
24
|
+
};
|
|
25
|
+
const versionMock = '1.0.0';
|
|
26
|
+
describe('handleLint', () => {
|
|
27
|
+
let processExitMock;
|
|
28
|
+
let exitCb;
|
|
29
|
+
const getMergedConfigMock = openapi_core_1.getMergedConfig;
|
|
30
|
+
beforeEach(() => {
|
|
31
|
+
jest.spyOn(process.stderr, 'write').mockImplementation(() => true);
|
|
32
|
+
perf_hooks_1.performance.now.mockImplementation(() => 42);
|
|
33
|
+
processExitMock = jest.spyOn(process, 'exit').mockImplementation();
|
|
34
|
+
jest.spyOn(process, 'once').mockImplementation((_e, cb) => {
|
|
35
|
+
exitCb = cb;
|
|
36
|
+
return process.on(_e, cb);
|
|
37
|
+
});
|
|
38
|
+
getMergedConfigMock.mockReturnValue(config_1.ConfigFixture);
|
|
39
|
+
});
|
|
40
|
+
afterEach(() => {
|
|
41
|
+
getMergedConfigMock.mockReset();
|
|
42
|
+
});
|
|
43
|
+
describe('loadConfig and getEnrtypoints stage', () => {
|
|
44
|
+
it('shoul call loadConfig and getFallbackEntryPointsOrExit', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
45
|
+
yield lint_1.handleLint(argvMock, versionMock);
|
|
46
|
+
expect(openapi_core_1.loadConfig).toHaveBeenCalledWith(undefined, undefined, undefined);
|
|
47
|
+
expect(utils_1.getFallbackEntryPointsOrExit).toHaveBeenCalled();
|
|
48
|
+
}));
|
|
49
|
+
it('should call loadConfig with args if such exist', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
50
|
+
yield lint_1.handleLint(Object.assign(Object.assign({}, argvMock), { config: '/path/redocly.yaml', extends: ['some/path'] }), versionMock);
|
|
51
|
+
expect(openapi_core_1.loadConfig).toHaveBeenCalledWith('/path/redocly.yaml', ['some/path'], undefined);
|
|
52
|
+
}));
|
|
53
|
+
it('should call mergedConfig with clear ignore if `generate-ignore-file` argv', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
54
|
+
yield lint_1.handleLint(Object.assign(Object.assign({}, argvMock), { 'generate-ignore-file': true }), versionMock);
|
|
55
|
+
expect(getMergedConfigMock).toHaveBeenCalled();
|
|
56
|
+
}));
|
|
57
|
+
});
|
|
58
|
+
describe('loop through entrypints and lint stage', () => {
|
|
59
|
+
it('should call getMergedConfig and lint ', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
60
|
+
yield lint_1.handleLint(argvMock, versionMock);
|
|
61
|
+
expect(perf_hooks_1.performance.now).toHaveBeenCalled();
|
|
62
|
+
expect(getMergedConfigMock).toHaveBeenCalled();
|
|
63
|
+
expect(openapi_core_1.lint).toHaveBeenCalled();
|
|
64
|
+
}));
|
|
65
|
+
it('should call skipRules,skipPreprocessors and addIgnore with argv', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
66
|
+
openapi_core_1.lint.mockResolvedValueOnce(['problem']);
|
|
67
|
+
yield lint_1.handleLint(Object.assign(Object.assign({}, argvMock), { 'skip-preprocessor': ['preprocessor'], 'skip-rule': ['rule'], 'generate-ignore-file': true }), versionMock);
|
|
68
|
+
expect(config_1.ConfigFixture.lint.skipRules).toHaveBeenCalledWith(['rule']);
|
|
69
|
+
expect(config_1.ConfigFixture.lint.skipPreprocessors).toHaveBeenCalledWith(['preprocessor']);
|
|
70
|
+
}));
|
|
71
|
+
it('should call formatProblems and getExecutionTime with argv', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
72
|
+
openapi_core_1.lint.mockResolvedValueOnce(['problem']);
|
|
73
|
+
yield lint_1.handleLint(Object.assign(Object.assign({}, argvMock), { 'max-problems': 2, format: 'stylish' }), versionMock);
|
|
74
|
+
expect(openapi_core_1.getTotals).toHaveBeenCalledWith(['problem']);
|
|
75
|
+
expect(openapi_core_1.formatProblems).toHaveBeenCalledWith(['problem'], {
|
|
76
|
+
format: 'stylish',
|
|
77
|
+
maxProblems: 2,
|
|
78
|
+
totals: { errors: 0 },
|
|
79
|
+
version: versionMock,
|
|
80
|
+
});
|
|
81
|
+
expect(utils_1.getExecutionTime).toHaveBeenCalledWith(42);
|
|
82
|
+
}));
|
|
83
|
+
it('should catch error in handleError if something fails', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
84
|
+
openapi_core_1.lint.mockRejectedValueOnce('error');
|
|
85
|
+
yield lint_1.handleLint(argvMock, versionMock);
|
|
86
|
+
expect(utils_1.handleError).toHaveBeenCalledWith('error', '');
|
|
87
|
+
}));
|
|
88
|
+
});
|
|
89
|
+
describe('erros and warning handle after lint stage', () => {
|
|
90
|
+
it('should call printLintTotals and printLintTotals', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
91
|
+
yield lint_1.handleLint(argvMock, versionMock);
|
|
92
|
+
expect(utils_1.printUnusedWarnings).toHaveBeenCalled();
|
|
93
|
+
}));
|
|
94
|
+
it('should call exit with 0 if no errors', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
95
|
+
yield lint_1.handleLint(argvMock, versionMock);
|
|
96
|
+
exitCb === null || exitCb === void 0 ? void 0 : exitCb();
|
|
97
|
+
expect(processExitMock).toHaveBeenCalledWith(0);
|
|
98
|
+
}));
|
|
99
|
+
it('should exit with 1 if tootals error > 0', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
100
|
+
openapi_core_1.getTotals.mockReturnValueOnce({ errors: 1 });
|
|
101
|
+
yield lint_1.handleLint(argvMock, versionMock);
|
|
102
|
+
exitCb === null || exitCb === void 0 ? void 0 : exitCb();
|
|
103
|
+
expect(processExitMock).toHaveBeenCalledWith(1);
|
|
104
|
+
}));
|
|
105
|
+
});
|
|
106
|
+
});
|
|
@@ -10,6 +10,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
10
10
|
};
|
|
11
11
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
12
|
const openapi_core_1 = require("@redocly/openapi-core");
|
|
13
|
+
const utils_1 = require("../../utils");
|
|
13
14
|
const push_1 = require("../../commands/push");
|
|
14
15
|
jest.mock('fs');
|
|
15
16
|
jest.mock('node-fetch', () => ({
|
|
@@ -32,7 +33,9 @@ describe('push', () => {
|
|
|
32
33
|
entrypoint: 'spec.json',
|
|
33
34
|
destination: '@org/my-api@1.0.0',
|
|
34
35
|
branchName: 'test',
|
|
35
|
-
'public': true
|
|
36
|
+
'public': true,
|
|
37
|
+
'batch-id': '123',
|
|
38
|
+
'batch-size': 2,
|
|
36
39
|
});
|
|
37
40
|
expect(redoclyClient.registryApi.prepareFileUpload).toBeCalledTimes(1);
|
|
38
41
|
expect(redoclyClient.registryApi.pushApi).toBeCalledTimes(1);
|
|
@@ -45,8 +48,34 @@ describe('push', () => {
|
|
|
45
48
|
organizationId: 'org',
|
|
46
49
|
rootFilePath: 'filePath',
|
|
47
50
|
version: '1.0.0',
|
|
51
|
+
batchId: '123',
|
|
52
|
+
batchSize: 2,
|
|
48
53
|
});
|
|
49
54
|
}));
|
|
55
|
+
it('fails if batchId value is an empty string', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
56
|
+
yield push_1.handlePush({
|
|
57
|
+
upsert: true,
|
|
58
|
+
entrypoint: 'spec.json',
|
|
59
|
+
destination: '@org/my-api@1.0.0',
|
|
60
|
+
branchName: 'test',
|
|
61
|
+
'public': true,
|
|
62
|
+
'batch-id': ' ',
|
|
63
|
+
'batch-size': 2,
|
|
64
|
+
});
|
|
65
|
+
expect(utils_1.exitWithError).toBeCalledTimes(1);
|
|
66
|
+
}));
|
|
67
|
+
it('fails if batchSize value is less than 2', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
68
|
+
yield push_1.handlePush({
|
|
69
|
+
upsert: true,
|
|
70
|
+
entrypoint: 'spec.json',
|
|
71
|
+
destination: '@org/my-api@1.0.0',
|
|
72
|
+
branchName: 'test',
|
|
73
|
+
'public': true,
|
|
74
|
+
'batch-id': '123',
|
|
75
|
+
'batch-size': 1,
|
|
76
|
+
});
|
|
77
|
+
expect(utils_1.exitWithError).toBeCalledTimes(1);
|
|
78
|
+
}));
|
|
50
79
|
});
|
|
51
80
|
describe('transformPush', () => {
|
|
52
81
|
it('should adapt the existing syntax', () => {
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/// <reference types="jest" />
|
|
2
|
+
export declare const ConfigFixture: {
|
|
3
|
+
configFile: null;
|
|
4
|
+
lint: {
|
|
5
|
+
addIgnore: jest.Mock<any, any>;
|
|
6
|
+
skipRules: jest.Mock<any, any>;
|
|
7
|
+
skipPreprocessors: jest.Mock<any, any>;
|
|
8
|
+
saveIgnore: jest.Mock<any, any>;
|
|
9
|
+
skipDecorators: jest.Mock<any, any>;
|
|
10
|
+
ignore: null;
|
|
11
|
+
};
|
|
12
|
+
};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ConfigFixture = void 0;
|
|
4
|
+
exports.ConfigFixture = {
|
|
5
|
+
configFile: null,
|
|
6
|
+
lint: {
|
|
7
|
+
addIgnore: jest.fn(),
|
|
8
|
+
skipRules: jest.fn(),
|
|
9
|
+
skipPreprocessors: jest.fn(),
|
|
10
|
+
saveIgnore: jest.fn(),
|
|
11
|
+
skipDecorators: jest.fn(),
|
|
12
|
+
ignore: null,
|
|
13
|
+
},
|
|
14
|
+
};
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
const utils_1 = require("../utils");
|
|
4
|
-
|
|
4
|
+
const colorette_1 = require("colorette");
|
|
5
|
+
jest.mock('os');
|
|
6
|
+
jest.mock('colorette');
|
|
5
7
|
describe('isSubdir', () => {
|
|
6
8
|
it('can correctly determine if subdir', () => {
|
|
7
9
|
[
|
|
@@ -39,3 +41,39 @@ describe('pathToFilename', () => {
|
|
|
39
41
|
expect(processedPath).toEqual('user_createWithList');
|
|
40
42
|
});
|
|
41
43
|
});
|
|
44
|
+
describe('printConfigLintTotals', () => {
|
|
45
|
+
const totalProblemsMock = {
|
|
46
|
+
errors: 1,
|
|
47
|
+
warnings: 0,
|
|
48
|
+
ignored: 0,
|
|
49
|
+
};
|
|
50
|
+
const redColoretteMocks = colorette_1.red;
|
|
51
|
+
const yellowColoretteMocks = colorette_1.yellow;
|
|
52
|
+
beforeEach(() => {
|
|
53
|
+
yellowColoretteMocks.mockImplementation((text) => text);
|
|
54
|
+
redColoretteMocks.mockImplementation((text) => text);
|
|
55
|
+
jest.spyOn(process.stderr, 'write').mockImplementation(() => true);
|
|
56
|
+
});
|
|
57
|
+
it('should print errors if such exist', () => {
|
|
58
|
+
utils_1.printConfigLintTotals(totalProblemsMock);
|
|
59
|
+
expect(process.stderr.write).toHaveBeenCalledWith('❌ Your config has 1 error.\n');
|
|
60
|
+
expect(redColoretteMocks).toHaveBeenCalledWith('❌ Your config has 1 error.\n');
|
|
61
|
+
});
|
|
62
|
+
it('should print warnign and error', () => {
|
|
63
|
+
utils_1.printConfigLintTotals(Object.assign(Object.assign({}, totalProblemsMock), { warnings: 2 }));
|
|
64
|
+
expect(process.stderr.write).toHaveBeenCalledWith('❌ Your config has 1 error and 2 warnings.\n');
|
|
65
|
+
expect(redColoretteMocks).toHaveBeenCalledWith('❌ Your config has 1 error and 2 warnings.\n');
|
|
66
|
+
});
|
|
67
|
+
it('should print warnign if no error', () => {
|
|
68
|
+
utils_1.printConfigLintTotals(Object.assign(Object.assign({}, totalProblemsMock), { errors: 0, warnings: 2 }));
|
|
69
|
+
expect(process.stderr.write).toHaveBeenCalledWith('You have 2 warnings.\n');
|
|
70
|
+
expect(yellowColoretteMocks).toHaveBeenCalledWith('You have 2 warnings.\n');
|
|
71
|
+
});
|
|
72
|
+
it('should print nothing if no error and no warnings', () => {
|
|
73
|
+
const result = utils_1.printConfigLintTotals(Object.assign(Object.assign({}, totalProblemsMock), { errors: 0 }));
|
|
74
|
+
expect(result).toBeUndefined();
|
|
75
|
+
expect(process.stderr.write).toHaveBeenCalledTimes(0);
|
|
76
|
+
expect(yellowColoretteMocks).toHaveBeenCalledTimes(0);
|
|
77
|
+
expect(redColoretteMocks).toHaveBeenCalledTimes(0);
|
|
78
|
+
});
|
|
79
|
+
});
|
package/lib/commands/join.d.ts
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
|
-
|
|
1
|
+
declare type JoinArgv = {
|
|
2
2
|
entrypoints: string[];
|
|
3
3
|
lint?: boolean;
|
|
4
4
|
'prefix-tags-with-info-prop'?: string;
|
|
5
5
|
'prefix-tags-with-filename'?: boolean;
|
|
6
6
|
'prefix-components-with-info-prop'?: string;
|
|
7
|
-
|
|
7
|
+
'without-x-tag-groups'?: boolean;
|
|
8
|
+
};
|
|
9
|
+
export declare function handleJoin(argv: JoinArgv, packageVersion: string): Promise<void>;
|
|
10
|
+
export {};
|
package/lib/commands/join.js
CHANGED
|
@@ -18,6 +18,8 @@ const openapi_core_1 = require("@redocly/openapi-core");
|
|
|
18
18
|
const utils_1 = require("../utils");
|
|
19
19
|
const js_utils_1 = require("../js-utils");
|
|
20
20
|
const COMPONENTS = 'components';
|
|
21
|
+
const Tags = 'tags';
|
|
22
|
+
const xTagGroups = 'x-tagGroups';
|
|
21
23
|
let potentialConflictsTotal = 0;
|
|
22
24
|
function handleJoin(argv, packageVersion) {
|
|
23
25
|
return __awaiter(this, void 0, void 0, function* () {
|
|
@@ -25,6 +27,15 @@ function handleJoin(argv, packageVersion) {
|
|
|
25
27
|
if (argv.entrypoints.length < 2) {
|
|
26
28
|
return utils_1.exitWithError(`At least 2 entrypoints should be provided. \n\n`);
|
|
27
29
|
}
|
|
30
|
+
const { 'prefix-components-with-info-prop': prefixComponentsWithInfoProp, 'prefix-tags-with-filename': prefixTagsWithFilename, 'prefix-tags-with-info-prop': prefixTagsWithInfoProp, 'without-x-tag-groups': withoutXTagGroups, } = argv;
|
|
31
|
+
const usedTagsOptions = [
|
|
32
|
+
prefixTagsWithFilename && 'prefix-tags-with-filename',
|
|
33
|
+
prefixTagsWithInfoProp && 'prefix-tags-with-info-prop',
|
|
34
|
+
withoutXTagGroups && 'without-x-tag-groups',
|
|
35
|
+
].filter(Boolean);
|
|
36
|
+
if (usedTagsOptions.length > 1) {
|
|
37
|
+
return utils_1.exitWithError(`You use ${colorette_1.yellow(usedTagsOptions.join(', '))} together.\nPlease choose only one! \n\n`);
|
|
38
|
+
}
|
|
28
39
|
const config = yield openapi_core_1.loadConfig();
|
|
29
40
|
const entrypoints = yield utils_1.getFallbackEntryPointsOrExit(argv.entrypoints, config);
|
|
30
41
|
const externalRefResolver = new openapi_core_1.BaseResolver(config.resolve);
|
|
@@ -67,14 +78,8 @@ function handleJoin(argv, packageVersion) {
|
|
|
67
78
|
tags: {},
|
|
68
79
|
paths: {},
|
|
69
80
|
components: {},
|
|
70
|
-
xWebhooks: {}
|
|
81
|
+
xWebhooks: {},
|
|
71
82
|
};
|
|
72
|
-
const prefixComponentsWithInfoProp = argv['prefix-components-with-info-prop'];
|
|
73
|
-
const prefixTagsWithFilename = argv['prefix-tags-with-filename'];
|
|
74
|
-
const prefixTagsWithInfoProp = argv['prefix-tags-with-info-prop'];
|
|
75
|
-
if (prefixTagsWithFilename && prefixTagsWithInfoProp) {
|
|
76
|
-
return utils_1.exitWithError(`You used ${colorette_1.yellow('prefix-tags-with-filename')} and ${colorette_1.yellow('prefix-tags-with-info-prop')} that do not go together.\nPlease choose only one! \n\n`);
|
|
77
|
-
}
|
|
78
83
|
addInfoSectionAndSpecVersion(documents, prefixComponentsWithInfoProp);
|
|
79
84
|
for (const document of documents) {
|
|
80
85
|
const openapi = document.parsed;
|
|
@@ -100,52 +105,78 @@ function handleJoin(argv, packageVersion) {
|
|
|
100
105
|
replace$Refs(openapi, componentsPrefix);
|
|
101
106
|
}
|
|
102
107
|
}
|
|
103
|
-
iteratePotentialConflicts(potentialConflicts);
|
|
108
|
+
iteratePotentialConflicts(potentialConflicts, withoutXTagGroups);
|
|
104
109
|
const specFilename = 'openapi.yaml';
|
|
105
110
|
const noRefs = true;
|
|
106
|
-
if (
|
|
107
|
-
utils_1.
|
|
111
|
+
if (potentialConflictsTotal) {
|
|
112
|
+
return utils_1.exitWithError(`Please fix conflicts before running ${colorette_1.yellow('join')}.`);
|
|
108
113
|
}
|
|
114
|
+
utils_1.writeYaml(joinedDef, specFilename, noRefs);
|
|
109
115
|
utils_1.printExecutionTime('join', startedAt, specFilename);
|
|
110
|
-
function populateTags({ entrypoint, entrypointFilename, tags, potentialConflicts, tagsPrefix, componentsPrefix }) {
|
|
111
|
-
const xTagGroups = 'x-tagGroups';
|
|
112
|
-
const Tags = 'tags';
|
|
116
|
+
function populateTags({ entrypoint, entrypointFilename, tags, potentialConflicts, tagsPrefix, componentsPrefix, }) {
|
|
113
117
|
if (!joinedDef.hasOwnProperty(Tags)) {
|
|
114
118
|
joinedDef[Tags] = [];
|
|
115
119
|
}
|
|
116
|
-
if (!joinedDef.hasOwnProperty(xTagGroups)) {
|
|
117
|
-
joinedDef[xTagGroups] = [];
|
|
118
|
-
}
|
|
119
120
|
if (!potentialConflicts.tags.hasOwnProperty('all')) {
|
|
120
121
|
potentialConflicts.tags['all'] = {};
|
|
121
122
|
}
|
|
122
|
-
if (!
|
|
123
|
-
|
|
124
|
-
}
|
|
125
|
-
const indexGroup = joinedDef[xTagGroups].findIndex((item) => item.name === entrypointFilename);
|
|
126
|
-
if (!joinedDef[xTagGroups][indexGroup].hasOwnProperty(Tags)) {
|
|
127
|
-
joinedDef[xTagGroups][indexGroup][Tags] = [];
|
|
123
|
+
if (withoutXTagGroups && !potentialConflicts.tags.hasOwnProperty('description')) {
|
|
124
|
+
potentialConflicts.tags['description'] = {};
|
|
128
125
|
}
|
|
129
126
|
for (const tag of tags) {
|
|
130
127
|
const entrypointTagName = addPrefix(tag.name, tagsPrefix);
|
|
131
128
|
if (tag.description) {
|
|
132
129
|
tag.description = addComponentsPrefix(tag.description, componentsPrefix);
|
|
133
130
|
}
|
|
134
|
-
|
|
131
|
+
const tagDuplicate = joinedDef.tags.find((t) => t.name === entrypointTagName);
|
|
132
|
+
if (tagDuplicate && withoutXTagGroups) {
|
|
133
|
+
// If tag already exist and `without-x-tag-groups` option;
|
|
134
|
+
// check if description are different for potential conflicts warning;
|
|
135
|
+
const isTagDescriptionNotEqual = tag.hasOwnProperty('description') && tagDuplicate.description !== tag.description;
|
|
136
|
+
potentialConflicts.tags.description[entrypointTagName].push(...(isTagDescriptionNotEqual ? [entrypoint] : []));
|
|
137
|
+
}
|
|
138
|
+
else {
|
|
139
|
+
// Instead add tag to joinedDef;
|
|
135
140
|
tag['x-displayName'] = tag['x-displayName'] || tag.name;
|
|
136
141
|
tag.name = entrypointTagName;
|
|
137
142
|
joinedDef.tags.push(tag);
|
|
143
|
+
if (withoutXTagGroups) {
|
|
144
|
+
potentialConflicts.tags.description[entrypointTagName] = [entrypoint];
|
|
145
|
+
}
|
|
138
146
|
}
|
|
139
|
-
if (!
|
|
140
|
-
|
|
147
|
+
if (!withoutXTagGroups) {
|
|
148
|
+
createXTagGroups(entrypointFilename);
|
|
149
|
+
populateXTagGroups(entrypointTagName, getIndexGroup(entrypointFilename));
|
|
141
150
|
}
|
|
142
|
-
const doesEntrypointExist = !potentialConflicts.tags.all[entrypointTagName] ||
|
|
143
|
-
|
|
151
|
+
const doesEntrypointExist = !potentialConflicts.tags.all[entrypointTagName] ||
|
|
152
|
+
(potentialConflicts.tags.all[entrypointTagName] &&
|
|
153
|
+
!potentialConflicts.tags.all[entrypointTagName].includes(entrypoint));
|
|
144
154
|
potentialConflicts.tags.all[entrypointTagName] = [
|
|
145
|
-
...(potentialConflicts.tags.all[entrypointTagName] || []),
|
|
155
|
+
...(potentialConflicts.tags.all[entrypointTagName] || []),
|
|
156
|
+
...(!withoutXTagGroups && doesEntrypointExist ? [entrypoint] : []),
|
|
146
157
|
];
|
|
147
158
|
}
|
|
148
159
|
}
|
|
160
|
+
function getIndexGroup(entrypointFilename) {
|
|
161
|
+
return joinedDef[xTagGroups].findIndex((item) => item.name === entrypointFilename);
|
|
162
|
+
}
|
|
163
|
+
function createXTagGroups(entrypointFilename) {
|
|
164
|
+
if (!joinedDef.hasOwnProperty(xTagGroups)) {
|
|
165
|
+
joinedDef[xTagGroups] = [];
|
|
166
|
+
}
|
|
167
|
+
if (!joinedDef[xTagGroups].some((g) => g.name === entrypointFilename)) {
|
|
168
|
+
joinedDef[xTagGroups].push({ name: entrypointFilename, tags: [] });
|
|
169
|
+
}
|
|
170
|
+
const indexGroup = getIndexGroup(entrypointFilename);
|
|
171
|
+
if (!joinedDef[xTagGroups][indexGroup].hasOwnProperty(Tags)) {
|
|
172
|
+
joinedDef[xTagGroups][indexGroup][Tags] = [];
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
function populateXTagGroups(entrypointTagName, indexGroup) {
|
|
176
|
+
if (!joinedDef[xTagGroups][indexGroup][Tags].find((t) => t.name === entrypointTagName)) {
|
|
177
|
+
joinedDef[xTagGroups][indexGroup][Tags].push(entrypointTagName);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
149
180
|
function collectServers(openapi) {
|
|
150
181
|
const { servers } = openapi;
|
|
151
182
|
if (servers) {
|
|
@@ -162,8 +193,7 @@ function handleJoin(argv, packageVersion) {
|
|
|
162
193
|
function collectInfoDescriptions(openapi, { entrypointFilename, componentsPrefix }) {
|
|
163
194
|
const { info } = openapi;
|
|
164
195
|
if (info === null || info === void 0 ? void 0 : info.description) {
|
|
165
|
-
const
|
|
166
|
-
const groupIndex = joinedDef[xTagGroups] ? joinedDef[xTagGroups].findIndex((item) => item.name === entrypointFilename) : -1;
|
|
196
|
+
const groupIndex = joinedDef[xTagGroups] ? getIndexGroup(entrypointFilename) : -1;
|
|
167
197
|
if (joinedDef.hasOwnProperty(xTagGroups) &&
|
|
168
198
|
groupIndex !== -1 &&
|
|
169
199
|
joinedDef[xTagGroups][groupIndex]['tags'] &&
|
|
@@ -306,7 +336,7 @@ function validateComponentsDifference(files) {
|
|
|
306
336
|
}
|
|
307
337
|
return isDiffer;
|
|
308
338
|
}
|
|
309
|
-
function iteratePotentialConflicts(potentialConflicts) {
|
|
339
|
+
function iteratePotentialConflicts(potentialConflicts, withoutXTagGroups) {
|
|
310
340
|
for (const group of Object.keys(potentialConflicts)) {
|
|
311
341
|
for (const [key, value] of Object.entries(potentialConflicts[group])) {
|
|
312
342
|
const conflicts = filterConflicts(value);
|
|
@@ -321,20 +351,28 @@ function iteratePotentialConflicts(potentialConflicts) {
|
|
|
321
351
|
}
|
|
322
352
|
}
|
|
323
353
|
else {
|
|
324
|
-
|
|
325
|
-
|
|
354
|
+
if (withoutXTagGroups && group === 'tags') {
|
|
355
|
+
duplicateTagDescriptionWarning(conflicts);
|
|
356
|
+
}
|
|
357
|
+
else {
|
|
358
|
+
potentialConflictsTotal += conflicts.length;
|
|
359
|
+
showConflicts(colorette_1.green(group) + ' => ' + key, conflicts);
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
if (group === 'tags' && !withoutXTagGroups) {
|
|
363
|
+
prefixTagSuggestion(conflicts.length);
|
|
326
364
|
}
|
|
327
|
-
prefixTagSuggestion(group, conflicts.length);
|
|
328
365
|
}
|
|
329
366
|
}
|
|
330
367
|
}
|
|
331
368
|
}
|
|
332
|
-
function
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
${
|
|
336
|
-
|
|
337
|
-
|
|
369
|
+
function duplicateTagDescriptionWarning(conflicts) {
|
|
370
|
+
const tagsKeys = conflicts.map(([tagName]) => `\`${tagName}\``);
|
|
371
|
+
const joinString = colorette_1.yellow(', ');
|
|
372
|
+
process.stderr.write(colorette_1.yellow(`\nwarning: ${tagsKeys.length} conflict(s) on the ${colorette_1.red(tagsKeys.join(joinString))} tags description.\n`));
|
|
373
|
+
}
|
|
374
|
+
function prefixTagSuggestion(conflictsLength) {
|
|
375
|
+
process.stderr.write(colorette_1.green(`\n${conflictsLength} conflict(s) on tags.\nSuggestion: please use ${colorette_1.blue('prefix-tags-with-filename')}, ${colorette_1.blue('prefix-tags-with-info-prop')} or ${colorette_1.blue('without-x-tag-groups')} to prevent naming conflicts.\n\n`));
|
|
338
376
|
}
|
|
339
377
|
function showConflicts(key, conflicts) {
|
|
340
378
|
for (const [path, files] of conflicts) {
|
package/lib/commands/lint.d.ts
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
|
-
import { OutputFormat } from '@redocly/openapi-core';
|
|
2
|
-
export declare
|
|
1
|
+
import { OutputFormat, RuleSeverity } from '@redocly/openapi-core';
|
|
2
|
+
export declare type LintOptions = {
|
|
3
3
|
entrypoints: string[];
|
|
4
4
|
'max-problems'?: number;
|
|
5
5
|
'generate-ignore-file'?: boolean;
|
|
6
6
|
'skip-rule'?: string[];
|
|
7
7
|
'skip-preprocessor'?: string[];
|
|
8
|
+
'lint-config': RuleSeverity;
|
|
8
9
|
extends?: string[];
|
|
9
10
|
config?: string;
|
|
10
11
|
format: OutputFormat;
|
|
11
|
-
}
|
|
12
|
+
};
|
|
13
|
+
export declare function handleLint(argv: LintOptions, version: string): Promise<void>;
|
package/lib/commands/lint.js
CHANGED
|
@@ -16,7 +16,7 @@ const colorette_1 = require("colorette");
|
|
|
16
16
|
const perf_hooks_1 = require("perf_hooks");
|
|
17
17
|
function handleLint(argv, version) {
|
|
18
18
|
return __awaiter(this, void 0, void 0, function* () {
|
|
19
|
-
const config = yield openapi_core_1.loadConfig(argv.config, argv.extends);
|
|
19
|
+
const config = yield openapi_core_1.loadConfig(argv.config, argv.extends, lintConfigCallback(argv, version));
|
|
20
20
|
const entrypoints = yield utils_1.getFallbackEntryPointsOrExit(argv.entrypoints, config);
|
|
21
21
|
if (argv['generate-ignore-file']) {
|
|
22
22
|
config.lint.ignore = {}; // clear ignore
|
|
@@ -78,3 +78,26 @@ function handleLint(argv, version) {
|
|
|
78
78
|
});
|
|
79
79
|
}
|
|
80
80
|
exports.handleLint = handleLint;
|
|
81
|
+
function lintConfigCallback(argv, version) {
|
|
82
|
+
if (argv['lint-config'] === 'off') {
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
return (config) => __awaiter(this, void 0, void 0, function* () {
|
|
86
|
+
const { 'max-problems': maxProblems, format } = argv;
|
|
87
|
+
const configPath = openapi_core_1.findConfig(argv.config) || '';
|
|
88
|
+
const stringYaml = openapi_core_1.stringifyYaml(config);
|
|
89
|
+
const configContent = openapi_core_1.makeDocumentFromString(stringYaml, configPath);
|
|
90
|
+
const problems = yield openapi_core_1.lintConfig({
|
|
91
|
+
document: configContent,
|
|
92
|
+
severity: argv['lint-config'],
|
|
93
|
+
});
|
|
94
|
+
const fileTotals = openapi_core_1.getTotals(problems);
|
|
95
|
+
openapi_core_1.formatProblems(problems, {
|
|
96
|
+
format,
|
|
97
|
+
maxProblems,
|
|
98
|
+
totals: fileTotals,
|
|
99
|
+
version,
|
|
100
|
+
});
|
|
101
|
+
utils_1.printConfigLintTotals(fileTotals);
|
|
102
|
+
});
|
|
103
|
+
}
|