@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.
Files changed (42) hide show
  1. package/lib/__mocks__/@redocly/openapi-core.d.ts +4 -0
  2. package/lib/__mocks__/@redocly/openapi-core.js +4 -5
  3. package/lib/__mocks__/perf_hooks.d.ts +4 -0
  4. package/lib/__mocks__/perf_hooks.js +6 -0
  5. package/lib/__mocks__/utils.d.ts +1 -3
  6. package/lib/__mocks__/utils.js +2 -2
  7. package/lib/__tests__/commands/join.test.d.ts +1 -0
  8. package/lib/__tests__/commands/join.test.js +41 -0
  9. package/lib/__tests__/commands/lint.test.d.ts +1 -0
  10. package/lib/__tests__/commands/lint.test.js +106 -0
  11. package/lib/__tests__/commands/push.test.js +30 -1
  12. package/lib/__tests__/fixtures/config.d.ts +12 -0
  13. package/lib/__tests__/fixtures/config.js +14 -0
  14. package/lib/__tests__/utils.test.js +39 -1
  15. package/lib/commands/join.d.ts +5 -2
  16. package/lib/commands/join.js +78 -40
  17. package/lib/commands/lint.d.ts +5 -3
  18. package/lib/commands/lint.js +24 -1
  19. package/lib/commands/preview-docs/preview-server/oauth2-redirect.html +1 -1
  20. package/lib/commands/preview-docs/preview-server/preview-server.js +3 -3
  21. package/lib/commands/push.d.ts +2 -1
  22. package/lib/commands/push.js +11 -1
  23. package/lib/index.js +27 -3
  24. package/lib/utils.d.ts +1 -0
  25. package/lib/utils.js +14 -2
  26. package/package.json +2 -2
  27. package/src/__mocks__/@redocly/openapi-core.ts +4 -4
  28. package/src/__mocks__/perf_hooks.ts +3 -0
  29. package/src/__mocks__/utils.ts +1 -1
  30. package/src/__tests__/commands/join.test.ts +47 -0
  31. package/src/__tests__/commands/lint.test.ts +133 -0
  32. package/src/__tests__/commands/push.test.ts +34 -1
  33. package/src/__tests__/fixtures/config.ts +11 -0
  34. package/src/__tests__/utils.test.ts +52 -2
  35. package/src/commands/join.ts +154 -64
  36. package/src/commands/lint.ts +58 -17
  37. package/src/commands/preview-docs/preview-server/oauth2-redirect.html +1 -1
  38. package/src/commands/preview-docs/preview-server/preview-server.ts +3 -3
  39. package/src/commands/push.ts +20 -2
  40. package/src/index.ts +38 -13
  41. package/src/utils.ts +19 -1
  42. 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();
@@ -0,0 +1,4 @@
1
+ /// <reference types="jest" />
2
+ export declare const performance: {
3
+ now: jest.Mock<any, any>;
4
+ };
@@ -0,0 +1,6 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.performance = void 0;
4
+ exports.performance = {
5
+ now: jest.fn()
6
+ };
@@ -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>;
@@ -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.getTotals = exports.getFallbackEntryPointsOrExit = void 0;
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
- jest.mock("os");
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
+ });
@@ -1,7 +1,10 @@
1
- export declare function handleJoin(argv: {
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
- }, packageVersion: string): Promise<void>;
7
+ 'without-x-tag-groups'?: boolean;
8
+ };
9
+ export declare function handleJoin(argv: JoinArgv, packageVersion: string): Promise<void>;
10
+ export {};
@@ -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 (!potentialConflictsTotal) {
107
- utils_1.writeYaml(joinedDef, specFilename, noRefs);
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 (!joinedDef[xTagGroups].some((g) => g.name === entrypointFilename)) {
123
- joinedDef[xTagGroups].push({ name: entrypointFilename, tags: [] });
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
- if (!joinedDef.tags.find((t) => t.name === entrypointTagName)) {
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 (!joinedDef[xTagGroups][indexGroup][Tags].find((t) => t === entrypointTagName)) {
140
- joinedDef[xTagGroups][indexGroup][Tags].push(entrypointTagName);
147
+ if (!withoutXTagGroups) {
148
+ createXTagGroups(entrypointFilename);
149
+ populateXTagGroups(entrypointTagName, getIndexGroup(entrypointFilename));
141
150
  }
142
- const doesEntrypointExist = !potentialConflicts.tags.all[entrypointTagName] || (potentialConflicts.tags.all[entrypointTagName] &&
143
- !potentialConflicts.tags.all[entrypointTagName].includes(entrypoint));
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] || []), ...(doesEntrypointExist ? [entrypoint] : [])
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 xTagGroups = 'x-tagGroups';
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
- showConflicts(colorette_1.green(group) + ' => ' + key, conflicts);
325
- potentialConflictsTotal += conflicts.length;
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 prefixTagSuggestion(group, conflictsLength) {
333
- if (group === 'tags') {
334
- process.stderr.write(colorette_1.green(`
335
- ${conflictsLength} conflict(s) on tags.
336
- Suggestion: please use ${colorette_1.blue('prefix-tags-with-filename')} or ${colorette_1.blue('prefix-tags-with-info-prop')} to prevent naming conflicts. \n\n`));
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) {
@@ -1,11 +1,13 @@
1
- import { OutputFormat } from '@redocly/openapi-core';
2
- export declare function handleLint(argv: {
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
- }, version: string): Promise<void>;
12
+ };
13
+ export declare function handleLint(argv: LintOptions, version: string): Promise<void>;
@@ -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
+ }